aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/jogamp/opengl/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/jogl/classes/jogamp/opengl/util')
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLArrayHandler.java43
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLArrayHandlerFlat.java18
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLArrayHandlerInterleaved.java67
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLDataArrayHandler.java58
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandler.java64
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandlerFlat.java51
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLVBOArrayHandler.java71
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/AudioSampleFormat.java64
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java79
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java1690
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java228
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java181
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java143
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/VideoPixelFormat.java191
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java449
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java1046
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGNatives.java82
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGStaticNatives.java41
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv08Natives.java78
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv09Natives.java78
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv10Natives.java78
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java142
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandler.java112
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerFlat.java76
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerInterleaved.java72
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/GLSLTextureRaster.java192
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java384
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncPipeline.java1245
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.fp28
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.vp6
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorLight.vp16
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorTexture.fp136
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncPoints.fp47
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncPoints.vp40
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_alphatest.fp33
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_attribute.glsl28
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_const.glsl31
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_lightdef.glsl3
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_settexcoord.vp6
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_uniform.glsl43
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_uniform_light.glsl1
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_varying.glsl2
-rw-r--r--src/jogl/classes/jogamp/opengl/util/jpeg/JPEGDecoder.java1520
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/FilterType.java71
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/FilterWriteStrategy.java20
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/ImageInfo.java64
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/ImageLine.java376
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/ImageLineHelper.java329
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/ImageLines.java107
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngDeinterlacer.java277
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java213
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngHelperInternal.java270
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkInputStream.java86
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java16
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngReader.java1416
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngWriter.java731
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngjBadCrcException.java6
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java10
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngjExceptionInternal.java24
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngjInputException.java6
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngjOutputException.java6
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngjUnsupportedException.java9
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java27
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkCopyBehaviour.java3
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkHelper.java433
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkList.java282
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkLoadBehaviour.java30
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkPredicate.java14
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java109
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunksList.java181
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunksListForWrite.java176
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunk.java266
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java64
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java68
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java42
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java42
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java53
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java31
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java26
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIHDR.java65
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java60
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkMultiple.java28
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkOFFS.java89
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java52
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPLTE.java49
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSBIT.java54
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java79
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSRGB.java36
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSTER.java60
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSingle.java45
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSkipped.java41
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java40
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTIME.java54
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTRNS.java270
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTextVar.java14
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java33
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java46
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java227
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/package.html5
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/DistortionMesh.java95
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDevice.java468
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceFactory.java43
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java605
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/ScaleAndOffset2D.java107
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_chroma.fp26
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_chroma.vp33
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_plain.fp22
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_plain.vp27
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_timewarp.vp44
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_timewarp_chroma.vp65
110 files changed, 13722 insertions, 4007 deletions
diff --git a/src/jogl/classes/jogamp/opengl/util/GLArrayHandler.java b/src/jogl/classes/jogamp/opengl/util/GLArrayHandler.java
index 22690b06d..810a9286b 100644
--- a/src/jogl/classes/jogamp/opengl/util/GLArrayHandler.java
+++ b/src/jogl/classes/jogamp/opengl/util/GLArrayHandler.java
@@ -31,41 +31,52 @@ package jogamp.opengl.util;
import javax.media.opengl.*;
/**
- * Handles consistency of buffer data and array state.
- * Implementations shall consider buffer types (VBO, ..), interleaved, etc.
- * They also need to consider array state types, i.e. fixed function or GLSL.
+ * Handles consistency of buffer data and array state.<br/>
+ * Implementations shall consider buffer types (VBO, ..), interleaved, etc.<br/>
+ * They also need to consider array state types, i.e. fixed function or GLSL.<br/>
*/
public interface GLArrayHandler {
+
/**
- * Implementation shall associate the data with the array
- * and synchronize the data with the GPU.
- *
+ * if <code>bind</code> is true and the data uses VBO,
+ * the latter will be bound and data written to the GPU if required.
+ * <p>
+ * If <code>bind</code> is false and the data uses VBO,
+ * the latter will be unbound.
+ * </p>
+ *
* @param gl current GL object
- * @param enable true if array data shall be valid, otherwise false.
- * @param ext extension object allowing passing of an implementation detail
+ * @param bind true if VBO shall be bound and data written,
+ * otherwise clear VBO binding.
+ * @return true if data uses VBO and action was performed, otherwise false
*/
- public void syncData(GL gl, boolean enable, Object ext);
-
+ public boolean bindBuffer(GL gl, boolean bind);
+
/**
* Implementation shall enable or disable the array state.
- *
+ * <p>
+ * Before enabling the array state,
+ * implementation shall synchronize the data with the GPU
+ * and associate the data with the array.
+ * </p>
+ *
* @param gl current GL object
* @param enable true if array shall be enabled, otherwise false.
- * @param ext extension object allowing passing of an implementation detail
+ * @param ext extension object allowing passing of an implementation detail
*/
public void enableState(GL gl, boolean enable, Object ext);
-
+
/**
- * Supporting interleaved arrays, where sub handlers may handle
+ * Supporting interleaved arrays, where sub handlers may handle
* the array state and the <i>master</i> handler the buffer consistency.
- *
+ *
* @param handler the sub handler
* @throws UnsupportedOperationException if this array handler does not support interleaved arrays
*/
public void addSubHandler(GLArrayHandlerFlat handler) throws UnsupportedOperationException;
public void setSubArrayVBOName(int vboName);
-
+
}
diff --git a/src/jogl/classes/jogamp/opengl/util/GLArrayHandlerFlat.java b/src/jogl/classes/jogamp/opengl/util/GLArrayHandlerFlat.java
index dca9129ad..179142fee 100644
--- a/src/jogl/classes/jogamp/opengl/util/GLArrayHandlerFlat.java
+++ b/src/jogl/classes/jogamp/opengl/util/GLArrayHandlerFlat.java
@@ -39,23 +39,21 @@ public interface GLArrayHandlerFlat {
/**
* Implementation shall associate the data with the array
- *
+ *
* @param gl current GL object
- * @param enable true if array data shall be valid, otherwise false.
- * @param force true force data association, bypassing optimization
- * @param ext extension object allowing passing of an implementation detail
+ * @param ext extension object allowing passing of an implementation detail
*/
- public void syncData(GL gl, boolean enable, boolean force, Object ext);
-
+ public void syncData(GL gl, Object ext);
+
/**
* Implementation shall enable or disable the array state.
- *
+ *
* @param gl current GL object
* @param enable true if array shall be enabled, otherwise false.
- * @param ext extension object allowing passing of an implementation detail
+ * @param ext extension object allowing passing of an implementation detail
*/
- public void enableState(GL gl, boolean enable, Object ext);
-
+ public void enableState(GL gl, boolean enable, Object ext);
+
public GLArrayDataWrapper getData();
}
diff --git a/src/jogl/classes/jogamp/opengl/util/GLArrayHandlerInterleaved.java b/src/jogl/classes/jogamp/opengl/util/GLArrayHandlerInterleaved.java
index d31b41582..3119b96ca 100644
--- a/src/jogl/classes/jogamp/opengl/util/GLArrayHandlerInterleaved.java
+++ b/src/jogl/classes/jogamp/opengl/util/GLArrayHandlerInterleaved.java
@@ -28,8 +28,6 @@
package jogamp.opengl.util;
-
-import java.nio.Buffer;
import java.util.ArrayList;
import java.util.List;
@@ -38,61 +36,46 @@ import javax.media.opengl.GL;
import com.jogamp.opengl.util.GLArrayDataEditable;
/**
- * Interleaved fixed function arrays, i.e. where this buffer data
- * represents many arrays.
+ * Interleaved fixed function arrays, i.e. where this buffer data
+ * represents many arrays.
*/
-public class GLArrayHandlerInterleaved implements GLArrayHandler {
- private GLArrayDataEditable ad;
- private List<GLArrayHandlerFlat> subArrays = new ArrayList<GLArrayHandlerFlat>();
+public class GLArrayHandlerInterleaved extends GLVBOArrayHandler {
+ private final List<GLArrayHandlerFlat> subArrays = new ArrayList<GLArrayHandlerFlat>();
- public GLArrayHandlerInterleaved(GLArrayDataEditable ad) {
- this.ad = ad;
+ public GLArrayHandlerInterleaved(final GLArrayDataEditable ad) {
+ super(ad);
}
-
- public final void setSubArrayVBOName(int vboName) {
+
+ @Override
+ public final void setSubArrayVBOName(final int vboName) {
for(int i=0; i<subArrays.size(); i++) {
subArrays.get(i).getData().setVBOName(vboName);
- }
+ }
}
-
- public final void addSubHandler(GLArrayHandlerFlat handler) {
+
+ @Override
+ public final void addSubHandler(final GLArrayHandlerFlat handler) {
subArrays.add(handler);
}
- private final void syncSubData(GL gl, boolean enable, boolean force, Object ext) {
+ private final void syncSubData(final GL gl, final Object ext) {
for(int i=0; i<subArrays.size(); i++) {
- subArrays.get(i).syncData(gl, enable, force, ext);
- }
- }
-
- public final void syncData(GL gl, boolean enable, Object ext) {
- if(enable) {
- final Buffer buffer = ad.getBuffer();
+ subArrays.get(i).syncData(gl, ext);
+ }
+ }
- if(ad.isVBO()) {
- // always bind and refresh the VBO mgr,
- // in case more than one gl*Pointer objects are in use
- gl.glBindBuffer(ad.getVBOTarget(), ad.getVBOName());
- if(!ad.isVBOWritten()) {
- if(null!=buffer) {
- gl.glBufferData(ad.getVBOTarget(), buffer.limit() * ad.getComponentSizeInBytes(), buffer, ad.getVBOUsage());
- }
- ad.setVBOWritten(true);
- }
- }
- syncSubData(gl, true, true, ext);
- } else {
- syncSubData(gl, false, true, ext);
- if(ad.isVBO()) {
- gl.glBindBuffer(ad.getVBOTarget(), 0);
+ @Override
+ public final void enableState(final GL gl, final boolean enable, final Object ext) {
+ if(enable) {
+ final boolean vboBound = bindBuffer(gl, true);
+ syncSubData(gl, ext);
+ if(vboBound) {
+ bindBuffer(gl, false);
}
}
- }
-
- public final void enableState(GL gl, boolean enable, Object ext) {
for(int i=0; i<subArrays.size(); i++) {
subArrays.get(i).enableState(gl, enable, ext);
- }
+ }
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/GLDataArrayHandler.java b/src/jogl/classes/jogamp/opengl/util/GLDataArrayHandler.java
index 6c8e2e762..66be98215 100644
--- a/src/jogl/classes/jogamp/opengl/util/GLDataArrayHandler.java
+++ b/src/jogl/classes/jogamp/opengl/util/GLDataArrayHandler.java
@@ -28,55 +28,43 @@
package jogamp.opengl.util;
-import javax.media.opengl.*;
+import javax.media.opengl.GL;
+import javax.media.opengl.GLException;
-import com.jogamp.opengl.util.*;
+import com.jogamp.opengl.util.GLArrayDataEditable;
-import java.nio.*;
/**
- * Used for pure VBO data arrays, i.e. where the buffer data
- * does not represents a specific array name.
+ * Used for pure VBO data arrays, i.e. where the buffer data
+ * does not represents a specific array name.
*/
-public class GLDataArrayHandler implements GLArrayHandler {
- private GLArrayDataEditable ad;
+public class GLDataArrayHandler extends GLVBOArrayHandler {
- public GLDataArrayHandler(GLArrayDataEditable ad) {
- this.ad = ad;
+ public GLDataArrayHandler(final GLArrayDataEditable ad) {
+ super(ad);
}
- public final void setSubArrayVBOName(int vboName) {
+ @Override
+ public final void setSubArrayVBOName(final int vboName) {
throw new UnsupportedOperationException();
}
-
- public final void addSubHandler(GLArrayHandlerFlat handler) {
+
+ @Override
+ public final void addSubHandler(final GLArrayHandlerFlat handler) {
throw new UnsupportedOperationException();
}
-
- public final void syncData(GL gl, boolean enable, Object ext) {
- if(!ad.isVBO()) {
- // makes no sense otherwise
- throw new GLException("GLDataArrayHandler can only handle VBOs.");
- }
- if(enable) {
- Buffer buffer = ad.getBuffer();
- // always bind and refresh the VBO mgr,
- // in case more than one gl*Pointer objects are in use
- gl.glBindBuffer(ad.getVBOTarget(), ad.getVBOName());
- if(!ad.isVBOWritten()) {
- if(null!=buffer) {
- gl.glBufferData(ad.getVBOTarget(), buffer.limit() * ad.getComponentSizeInBytes(), buffer, ad.getVBOUsage());
- }
- ad.setVBOWritten(true);
+ @Override
+ public final void enableState(final GL gl, final boolean enable, final Object ext) {
+ if(enable) {
+ if(!ad.isVBO()) {
+ // makes no sense otherwise
+ throw new GLException("GLDataArrayHandler can only handle VBOs.");
}
- } else {
- gl.glBindBuffer(ad.getVBOTarget(), 0);
- }
- }
-
- public final void enableState(GL gl, boolean enable, Object ext) {
- // no array association
+ bindBuffer(gl, true);
+ bindBuffer(gl, false);
+ }
+ // no array association
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandler.java b/src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandler.java
index d8939dc0f..f5869c6ba 100644
--- a/src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandler.java
+++ b/src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandler.java
@@ -28,47 +28,36 @@
package jogamp.opengl.util;
-import javax.media.opengl.*;
-import javax.media.opengl.fixedfunc.*;
+import javax.media.opengl.GL;
+import javax.media.opengl.GLException;
+import javax.media.opengl.fixedfunc.GLPointerFunc;
import com.jogamp.opengl.util.GLArrayDataEditable;
-import java.nio.*;
-
/**
- * Used for 1:1 fixed function arrays, i.e. where the buffer data
- * represents this array only.
+ * Used for 1:1 fixed function arrays, i.e. where the buffer data
+ * represents this array only.
*/
-public class GLFixedArrayHandler implements GLArrayHandler {
- private GLArrayDataEditable ad;
-
- public GLFixedArrayHandler(GLArrayDataEditable ad) {
- this.ad = ad;
+public class GLFixedArrayHandler extends GLVBOArrayHandler {
+ public GLFixedArrayHandler(final GLArrayDataEditable ad) {
+ super(ad);
}
-
- public final void setSubArrayVBOName(int vboName) {
+
+ @Override
+ public final void setSubArrayVBOName(final int vboName) {
throw new UnsupportedOperationException();
}
-
- public final void addSubHandler(GLArrayHandlerFlat handler) {
+
+ @Override
+ public final void addSubHandler(final GLArrayHandlerFlat handler) {
throw new UnsupportedOperationException();
}
-
- public final void syncData(GL gl, boolean enable, Object ext) {
+
+ @Override
+ public final void enableState(final GL gl, final boolean enable, final Object ext) {
+ final GLPointerFunc glp = gl.getGL2ES1();
if(enable) {
- final Buffer buffer = ad.getBuffer();
- if(ad.isVBO()) {
- // always bind and refresh the VBO mgr,
- // in case more than one gl*Pointer objects are in use
- gl.glBindBuffer(ad.getVBOTarget(), ad.getVBOName());
- if(!ad.isVBOWritten()) {
- if(null!=buffer) {
- gl.glBufferData(ad.getVBOTarget(), buffer.limit() * ad.getComponentSizeInBytes(), buffer, ad.getVBOUsage());
- }
- ad.setVBOWritten(true);
- }
- }
- final GLPointerFunc glp = gl.getGL2ES1();
+ final boolean vboBound = bindBuffer(gl, true);
switch(ad.getIndex()) {
case GLPointerFunc.GL_VERTEX_ARRAY:
glp.glVertexPointer(ad);
@@ -83,17 +72,12 @@ public class GLFixedArrayHandler implements GLArrayHandler {
glp.glTexCoordPointer(ad);
break;
default:
- throw new GLException("invalid glArrayIndex: "+ad.getIndex()+":\n\t"+ad);
+ throw new GLException("invalid glArrayIndex: "+ad.getIndex()+":\n\t"+ad);
}
- } else if(ad.isVBO()) {
- gl.glBindBuffer(ad.getVBOTarget(), 0);
- }
- }
-
- public final void enableState(GL gl, boolean enable, Object ext) {
- final GLPointerFunc glp = gl.getGL2ES1();
- if(enable) {
- glp.glEnableClientState(ad.getIndex());
+ if(vboBound) {
+ bindBuffer(gl, false);
+ }
+ glp.glEnableClientState(ad.getIndex());
} else {
glp.glDisableClientState(ad.getIndex());
}
diff --git a/src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandlerFlat.java b/src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandlerFlat.java
index 2937cc720..b5fa2f0e5 100644
--- a/src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandlerFlat.java
+++ b/src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandlerFlat.java
@@ -35,46 +35,47 @@ import javax.media.opengl.fixedfunc.GLPointerFunc;
import com.jogamp.opengl.util.GLArrayDataWrapper;
/**
- * Used for interleaved fixed function arrays, i.e. where the buffer data itself is handled
+ * Used for interleaved fixed function arrays, i.e. where the buffer data itself is handled
* separately and interleaves many arrays.
*/
public class GLFixedArrayHandlerFlat implements GLArrayHandlerFlat {
- private GLArrayDataWrapper ad;
+ private final GLArrayDataWrapper ad;
- public GLFixedArrayHandlerFlat(GLArrayDataWrapper ad) {
+ public GLFixedArrayHandlerFlat(final GLArrayDataWrapper ad) {
this.ad = ad;
}
+ @Override
public GLArrayDataWrapper getData() {
return ad;
}
-
- public final void syncData(GL gl, boolean enable, boolean force, Object ext) {
- if(enable) {
- final GLPointerFunc glp = gl.getGL2ES1();
- switch(ad.getIndex()) {
- case GLPointerFunc.GL_VERTEX_ARRAY:
- glp.glVertexPointer(ad);
- break;
- case GLPointerFunc.GL_NORMAL_ARRAY:
- glp.glNormalPointer(ad);
- break;
- case GLPointerFunc.GL_COLOR_ARRAY:
- glp.glColorPointer(ad);
- break;
- case GLPointerFunc.GL_TEXTURE_COORD_ARRAY:
- glp.glTexCoordPointer(ad);
- break;
- default:
- throw new GLException("invalid glArrayIndex: "+ad.getIndex()+":\n\t"+ad);
- }
+
+ @Override
+ public final void syncData(final GL gl, final Object ext) {
+ final GLPointerFunc glp = gl.getGL2ES1();
+ switch(ad.getIndex()) {
+ case GLPointerFunc.GL_VERTEX_ARRAY:
+ glp.glVertexPointer(ad);
+ break;
+ case GLPointerFunc.GL_NORMAL_ARRAY:
+ glp.glNormalPointer(ad);
+ break;
+ case GLPointerFunc.GL_COLOR_ARRAY:
+ glp.glColorPointer(ad);
+ break;
+ case GLPointerFunc.GL_TEXTURE_COORD_ARRAY:
+ glp.glTexCoordPointer(ad);
+ break;
+ default:
+ throw new GLException("invalid glArrayIndex: "+ad.getIndex()+":\n\t"+ad);
}
}
- public final void enableState(GL gl, boolean enable, Object ext) {
+ @Override
+ public final void enableState(final GL gl, final boolean enable, final Object ext) {
final GLPointerFunc glp = gl.getGL2ES1();
if(enable) {
- glp.glEnableClientState(ad.getIndex());
+ glp.glEnableClientState(ad.getIndex());
} else {
glp.glDisableClientState(ad.getIndex());
}
diff --git a/src/jogl/classes/jogamp/opengl/util/GLVBOArrayHandler.java b/src/jogl/classes/jogamp/opengl/util/GLVBOArrayHandler.java
new file mode 100644
index 000000000..7bc1ef1ef
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/GLVBOArrayHandler.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright 2010 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+
+package jogamp.opengl.util;
+
+import java.nio.Buffer;
+
+import javax.media.opengl.GL;
+
+import com.jogamp.opengl.util.GLArrayDataEditable;
+
+/**
+ * Interleaved fixed function arrays, i.e. where this buffer data
+ * represents many arrays.
+ */
+public abstract class GLVBOArrayHandler implements GLArrayHandler {
+ protected GLArrayDataEditable ad;
+
+ public GLVBOArrayHandler(final GLArrayDataEditable ad) {
+ this.ad = ad;
+ }
+
+ @Override
+ public final boolean bindBuffer(final GL gl, final boolean bind) {
+ if( !ad.isVBO() ) {
+ return false;
+ }
+ if(bind) {
+ // always bind and refresh the VBO mgr,
+ // in case more than one gl*Pointer objects are in use
+ gl.glBindBuffer(ad.getVBOTarget(), ad.getVBOName());
+ if(!ad.isVBOWritten()) {
+ final Buffer buffer = ad.getBuffer();
+ if(null!=buffer) {
+ gl.glBufferData(ad.getVBOTarget(), buffer.limit() * ad.getComponentSizeInBytes(), buffer, ad.getVBOUsage());
+ }
+ ad.setVBOWritten(true);
+ }
+ } else {
+ gl.glBindBuffer(ad.getVBOTarget(), 0);
+ }
+ return true;
+ }
+
+}
+
diff --git a/src/jogl/classes/jogamp/opengl/util/av/AudioSampleFormat.java b/src/jogl/classes/jogamp/opengl/util/av/AudioSampleFormat.java
new file mode 100644
index 000000000..4c48b90e8
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/AudioSampleFormat.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.av;
+
+/** FFMPEG/libAV compatible audio sample formats */
+public enum AudioSampleFormat {
+ // NONE = -1,
+ U8, ///< unsigned 8 bits
+ S16, ///< signed 16 bits
+ S32, ///< signed 32 bits
+ FLT, ///< float
+ DBL, ///< double
+
+ U8P, ///< unsigned 8 bits, planar
+ S16P, ///< signed 16 bits, planar
+ S32P, ///< signed 32 bits, planar
+ FLTP, ///< float, planar
+ DBLP, ///< double, planar
+
+ COUNT; ///< Number of sample formats.
+
+ /**
+ * Returns the matching SampleFormat value corresponding to the given SampleFormat's integer ordinal.
+ * <pre>
+ * given:
+ * ordinal = enumValue.ordinal()
+ * reverse:
+ * enumValue = EnumClass.values()[ordinal]
+ * </pre>
+ * @throws IllegalArgumentException if the given ordinal is out of range, i.e. not within [ 0 .. SampleFormat.values().length-1 ]
+ */
+ public static AudioSampleFormat valueOf(final int ordinal) throws IllegalArgumentException {
+ final AudioSampleFormat[] all = AudioSampleFormat.values();
+ if( 0 <= ordinal && ordinal < all.length ) {
+ return all[ordinal];
+ }
+ throw new IllegalArgumentException("Ordinal "+ordinal+" out of range of SampleFormat.values()[0.."+(all.length-1)+"]");
+ }
+} \ No newline at end of file
diff --git a/src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java
index 6bf8839af..f9df9153f 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java
@@ -3,14 +3,14 @@
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
- *
+ *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
@@ -20,7 +20,7 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
@@ -44,72 +44,69 @@ import jogamp.opengl.egl.EGLExt;
public abstract class EGLMediaPlayerImpl extends GLMediaPlayerImpl {
final protected TextureType texType;
final protected boolean useKHRSync;
-
+
public enum TextureType {
- GL(0), KHRImage(1);
-
+ GL(0), KHRImage(1);
+
public final int id;
- TextureType(int id){
+ TextureType(final int id){
this.id = id;
}
- }
-
+ }
+
public static class EGLTextureFrame extends TextureSequence.TextureFrame {
-
- public EGLTextureFrame(Buffer clientBuffer, Texture t, long khrImage, long khrSync) {
+
+ public EGLTextureFrame(final Buffer clientBuffer, final Texture t, final long khrImage, final long khrSync) {
super(t);
this.clientBuffer = clientBuffer;
this.image = khrImage;
this.sync = khrSync;
}
-
+
public final Buffer getClientBuffer() { return clientBuffer; }
- public final long getImage() { return image; }
+ public final long getImage() { return image; }
public final long getSync() { return sync; }
-
+
+ @Override
public String toString() {
- return "EGLTextureFrame[" + texture + ", img "+ image + ", sync "+ sync+", clientBuffer "+clientBuffer+"]";
+ return "EGLTextureFrame[pts " + pts + " ms, l " + duration + " ms, texID "+ texture.getTextureObject() + ", img "+ image + ", sync "+ sync+", clientBuffer "+clientBuffer+"]";
}
protected final Buffer clientBuffer;
protected final long image;
protected final long sync;
}
-
- protected EGLMediaPlayerImpl() {
- this(TextureType.GL, false);
- }
-
- protected EGLMediaPlayerImpl(TextureType texType, boolean useKHRSync) {
+
+ protected EGLMediaPlayerImpl(final TextureType texType, final boolean useKHRSync) {
super();
this.texType = texType;
this.useKHRSync = useKHRSync;
}
@Override
- protected TextureSequence.TextureFrame createTexImage(GL gl, int idx, int[] tex) {
- final Texture texture = super.createTexImageImpl(gl, idx, tex, width, height, false);
+ protected TextureSequence.TextureFrame createTexImage(final GL gl, final int texName) {
+ final Texture texture = super.createTexImageImpl(gl, texName, getWidth(), getHeight());
final Buffer clientBuffer;
final long image;
final long sync;
- final boolean eglUsage = TextureType.KHRImage == texType || useKHRSync ;
+ final boolean eglUsage = TextureType.KHRImage == texType || useKHRSync ;
final EGLContext eglCtx;
final EGLExt eglExt;
final EGLDrawable eglDrawable;
-
+
if(eglUsage) {
eglCtx = (EGLContext) gl.getContext();
eglExt = eglCtx.getEGLExt();
- eglDrawable = (EGLDrawable) eglCtx.getGLDrawable();
+ eglDrawable = (EGLDrawable) eglCtx.getGLDrawable();
} else {
eglCtx = null;
eglExt = null;
eglDrawable = null;
}
-
+
if(TextureType.KHRImage == texType) {
- IntBuffer nioTmp = Buffers.newDirectIntBuffer(1);
+ final IntBuffer nioTmp = Buffers.newDirectIntBuffer(1);
// create EGLImage from texture
clientBuffer = null; // FIXME
nioTmp.put(0, EGL.EGL_NONE);
@@ -117,7 +114,7 @@ public abstract class EGLMediaPlayerImpl extends GLMediaPlayerImpl {
EGLExt.EGL_GL_TEXTURE_2D_KHR,
clientBuffer, nioTmp);
if (0==image) {
- throw new RuntimeException("EGLImage creation failed: "+EGL.eglGetError()+", ctx "+eglCtx+", tex "+tex[idx]+", err "+toHexString(EGL.eglGetError()));
+ throw new RuntimeException("EGLImage creation failed: "+EGL.eglGetError()+", ctx "+eglCtx+", tex "+texName+", err "+toHexString(EGL.eglGetError()));
}
} else {
clientBuffer = null;
@@ -125,12 +122,12 @@ public abstract class EGLMediaPlayerImpl extends GLMediaPlayerImpl {
}
if(useKHRSync) {
- int[] tmp = new int[1];
+ final IntBuffer tmp = Buffers.newDirectIntBuffer(1);
// Create sync object so that we can be sure that gl has finished
// rendering the EGLImage texture before we tell OpenMAX to fill
// it with a new frame.
- tmp[0] = EGL.EGL_NONE;
- sync = eglExt.eglCreateSyncKHR(eglDrawable.getNativeSurface().getDisplayHandle(), EGLExt.EGL_SYNC_FENCE_KHR, tmp, 0);
+ tmp.put(0, EGL.EGL_NONE);
+ sync = eglExt.eglCreateSyncKHR(eglDrawable.getNativeSurface().getDisplayHandle(), EGLExt.EGL_SYNC_FENCE_KHR, tmp);
if (0==sync) {
throw new RuntimeException("EGLSync creation failed: "+EGL.eglGetError()+", ctx "+eglCtx+", err "+toHexString(EGL.eglGetError()));
}
@@ -139,31 +136,31 @@ public abstract class EGLMediaPlayerImpl extends GLMediaPlayerImpl {
}
return new EGLTextureFrame(clientBuffer, texture, image, sync);
}
-
+
@Override
- protected void destroyTexImage(GL gl, TextureSequence.TextureFrame imgTex) {
- final boolean eglUsage = TextureType.KHRImage == texType || useKHRSync ;
+ protected void destroyTexFrame(final GL gl, final TextureSequence.TextureFrame frame) {
+ final boolean eglUsage = TextureType.KHRImage == texType || useKHRSync ;
final EGLContext eglCtx;
final EGLExt eglExt;
final EGLDrawable eglDrawable;
-
+
if(eglUsage) {
eglCtx = (EGLContext) gl.getContext();
eglExt = eglCtx.getEGLExt();
- eglDrawable = (EGLDrawable) eglCtx.getGLDrawable();
+ eglDrawable = (EGLDrawable) eglCtx.getGLDrawable();
} else {
eglCtx = null;
eglExt = null;
eglDrawable = null;
}
- final EGLTextureFrame eglTex = (EGLTextureFrame) imgTex;
-
+ final EGLTextureFrame eglTex = (EGLTextureFrame) frame;
+
if(0!=eglTex.getImage()) {
eglExt.eglDestroyImageKHR(eglDrawable.getNativeSurface().getDisplayHandle(), eglTex.getImage());
}
if(0!=eglTex.getSync()) {
eglExt.eglDestroySyncKHR(eglDrawable.getNativeSurface().getDisplayHandle(), eglTex.getSync());
}
- super.destroyTexImage(gl, imgTex);
+ super.destroyTexFrame(gl, frame);
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java
index 27c926704..0969199c6 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java
@@ -3,14 +3,14 @@
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
- *
+ *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
@@ -20,7 +20,7 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
@@ -28,336 +28,688 @@
package jogamp.opengl.util.av;
import java.io.IOException;
-import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
+import javax.media.nativewindow.AbstractGraphicsDevice;
import javax.media.opengl.GL;
-import javax.media.opengl.GL2;
+import javax.media.opengl.GL2GL3;
+import javax.media.opengl.GLContext;
+import javax.media.opengl.GLDrawable;
+import javax.media.opengl.GLDrawableFactory;
import javax.media.opengl.GLES2;
import javax.media.opengl.GLException;
+import javax.media.opengl.GLProfile;
+
+import jogamp.opengl.Debug;
+import com.jogamp.common.net.UriQueryProps;
+import com.jogamp.common.net.Uri;
+import com.jogamp.common.os.Platform;
+import com.jogamp.common.util.LFRingbuffer;
+import com.jogamp.common.util.Ringbuffer;
+import com.jogamp.opengl.GLExtensions;
+import com.jogamp.opengl.util.TimeFrameI;
+import com.jogamp.opengl.util.av.AudioSink;
import com.jogamp.opengl.util.av.GLMediaPlayer;
+import com.jogamp.opengl.util.glsl.ShaderCode;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureSequence;
+import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame;
/**
* After object creation an implementation may customize the behavior:
* <ul>
- * <li>{@link #setTextureCount(int)}</li>
+ * <li>{@link #setDesTextureCount(int)}</li>
* <li>{@link #setTextureTarget(int)}</li>
* <li>{@link EGLMediaPlayerImpl#setEGLTexImageAttribs(boolean, boolean)}.</li>
* </ul>
- *
+ *
* <p>
* See {@link GLMediaPlayer}.
* </p>
*/
public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
+ private static final int STREAM_WORKER_DELAY = Debug.getIntProperty("jogl.debug.GLMediaPlayer.StreamWorker.delay", false, 0);
+
+ private static final String unknown = "unknown";
+
+ private volatile State state;
+ private final Object stateLock = new Object();
+
+ private int textureCount;
+ private int textureTarget;
+ private int textureFormat;
+ private int textureInternalFormat;
+ private int textureType;
+ private int texUnit;
+
+ private int textureFragmentShaderHashCode;
+
+ private final int[] texMinMagFilter = { GL.GL_NEAREST, GL.GL_NEAREST };
+ private final int[] texWrapST = { GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE };
+
+ /** User requested URI stream location. */
+ private Uri streamLoc = null;
+
+ /**
+ * In case {@link #streamLoc} is a {@link GLMediaPlayer#CameraInputScheme},
+ * {@link #cameraPath} holds the URI's path portion
+ * as parsed in {@link #initStream(Uri, int, int, int)}.
+ * @see #cameraProps
+ */
+ protected Uri.Encoded cameraPath = null;
+ /** Optional camera properties, see {@link #cameraPath}. */
+ protected Map<String, String> cameraProps = null;
+
+ private volatile float playSpeed = 1.0f;
+ private float audioVolume = 1.0f;
+
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private int vid = GLMediaPlayer.STREAM_ID_AUTO;
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private int aid = GLMediaPlayer.STREAM_ID_AUTO;
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private int width = 0;
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private int height = 0;
+ /** Video avg. fps. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private float fps = 0;
+ /** Video avg. frame duration in ms. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private float frame_duration = 0f;
+ /** Stream bps. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private int bps_stream = 0;
+ /** Video bps. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private int bps_video = 0;
+ /** Audio bps. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private int bps_audio = 0;
+ /** In frames. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private int videoFrames = 0;
+ /** In frames. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private int audioFrames = 0;
+ /** In ms. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private int duration = 0;
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private String acodec = unknown;
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ private String vcodec = unknown;
+
+ private volatile int decodedFrameCount = 0;
+ private int presentedFrameCount = 0;
+ private int displayedFrameCount = 0;
+ private volatile int video_pts_last = 0;
+
+ /**
+ * Help detect EOS, limit is {@link #MAX_FRAMELESS_MS_UNTIL_EOS}.
+ * To be used either by getNextTexture(..) or StreamWorker for audio-only.
+ */
+ private int nullFrameCount = 0;
+ private int maxNullFrameCountUntilEOS = 0;
+ /**
+ * Help detect EOS, limit {@value} milliseconds without a valid frame.
+ */
+ private static final int MAX_FRAMELESS_MS_UNTIL_EOS = 5000;
+ private static final int MAX_FRAMELESS_UNTIL_EOS_DEFAULT = MAX_FRAMELESS_MS_UNTIL_EOS / 30; // default value assuming 30fps
+
+ /** See {@link #getAudioSink()}. Set by implementation if used from within {@link #initStreamImpl(int, int)}! */
+ protected AudioSink audioSink = null;
+ protected boolean audioSinkPlaySpeedSet = false;
- protected static final String unknown = "unknown";
-
- protected State state;
- protected int textureCount;
- protected int textureTarget;
- protected int textureFormat;
- protected int textureInternalFormat;
- protected int textureType;
- protected int texUnit;
-
-
- protected int[] texMinMagFilter = { GL.GL_NEAREST, GL.GL_NEAREST };
- protected int[] texWrapST = { GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE };
-
- protected URLConnection urlConn = null;
-
- protected float playSpeed = 1.0f;
-
- /** Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
- protected int width = 0;
- /** Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
- protected int height = 0;
- /** Video fps. Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
- protected float fps = 0;
- /** Stream bps. Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
- protected int bps_stream = 0;
- /** Video bps. Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
- protected int bps_video = 0;
- /** Audio bps. Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
- protected int bps_audio = 0;
- /** In frames. Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
- protected int totalFrames = 0;
- /** In ms. Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
- protected int duration = 0;
- /** Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
- protected String acodec = unknown;
- /** Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
- protected String vcodec = unknown;
-
- protected int frameNumber = 0;
-
- protected TextureSequence.TextureFrame[] texFrames = null;
- protected HashMap<Integer, TextureSequence.TextureFrame> texFrameMap = new HashMap<Integer, TextureSequence.TextureFrame>();
- private ArrayList<GLMediaEventListener> eventListeners = new ArrayList<GLMediaEventListener>();
+ /** System Clock Reference (SCR) of first audio PTS at start time. */
+ private long audio_scr_t0 = 0;
+ private boolean audioSCR_reset = true;
+
+ /** System Clock Reference (SCR) of first video frame at start time. */
+ private long video_scr_t0 = 0;
+ /** System Clock Reference (SCR) PTS offset, i.e. first video PTS at start time. */
+ private int video_scr_pts = 0;
+ /** Cumulative video pts diff. */
+ private float video_dpts_cum = 0;
+ /** Cumulative video frames. */
+ private int video_dpts_count = 0;
+ /** Number of min frame count required for video cumulative sync. */
+ private static final int VIDEO_DPTS_NUM = 20;
+ /** Cumulative coefficient, value {@value}. */
+ private static final float VIDEO_DPTS_COEFF = 0.7943282f; // (float) Math.exp(Math.log(0.01) / VIDEO_DPTS_NUM);
+ /** Maximum valid video pts diff. */
+ private static final int VIDEO_DPTS_MAX = 5000; // 5s max diff
+ /** Trigger video PTS reset with given cause as bitfield. */
+ private boolean videoSCR_reset = false;
+
+ private TextureFrame[] videoFramesOrig = null;
+ private Ringbuffer<TextureFrame> videoFramesFree = null;
+ private Ringbuffer<TextureFrame> videoFramesDecoded = null;
+ private volatile TextureFrame lastFrame = null;
+ /**
+ * @see #isGLOriented()
+ */
+ private boolean isInGLOrientation = false;
+
+ private final ArrayList<GLMediaEventListener> eventListeners = new ArrayList<GLMediaEventListener>();
protected GLMediaPlayerImpl() {
- this.textureCount=3;
+ this.textureCount=0;
this.textureTarget=GL.GL_TEXTURE_2D;
this.textureFormat = GL.GL_RGBA;
this.textureInternalFormat = GL.GL_RGBA;
- this.textureType = GL.GL_UNSIGNED_BYTE;
+ this.textureType = GL.GL_UNSIGNED_BYTE;
this.texUnit = 0;
+ this.textureFragmentShaderHashCode = 0;
this.state = State.Uninitialized;
}
@Override
- public void setTextureUnit(int u) { texUnit = u; }
-
+ public final void setTextureUnit(final int u) { texUnit = u; }
+
+ @Override
+ public final int getTextureUnit() { return texUnit; }
+
@Override
- public int getTextureUnit() { return texUnit; }
-
- protected final void setTextureCount(int textureCount) {
- this.textureCount=textureCount;
- }
+ public final int getTextureTarget() { return textureTarget; }
+
+ protected final int getTextureFormat() { return textureFormat; }
+
+ protected final int getTextureType() { return textureType; }
+
@Override
public final int getTextureCount() { return textureCount; }
-
- protected final void setTextureTarget(int target) { textureTarget=target; }
- protected final void setTextureFormat(int internalFormat, int format) {
- textureInternalFormat=internalFormat;
- textureFormat=format;
- }
- protected final void setTextureType(int t) { textureType=t; }
-
- public final void setTextureMinMagFilter(int[] minMagFilter) { texMinMagFilter[0] = minMagFilter[0]; texMinMagFilter[1] = minMagFilter[1];}
+
+ protected final void setTextureTarget(final int target) { textureTarget=target; }
+ protected final void setTextureFormat(final int internalFormat, final int format) {
+ textureInternalFormat=internalFormat;
+ textureFormat=format;
+ }
+ protected final void setTextureType(final int t) { textureType=t; }
+
+ @Override
+ public final void setTextureMinMagFilter(final int[] minMagFilter) { texMinMagFilter[0] = minMagFilter[0]; texMinMagFilter[1] = minMagFilter[1];}
+ @Override
public final int[] getTextureMinMagFilter() { return texMinMagFilter; }
-
- public final void setTextureWrapST(int[] wrapST) { texWrapST[0] = wrapST[0]; texWrapST[1] = wrapST[1];}
- public final int[] getTextureWrapST() { return texWrapST; }
@Override
- public final TextureSequence.TextureFrame getLastTexture() throws IllegalStateException {
- if(State.Uninitialized == state) {
- throw new IllegalStateException("Instance not initialized: "+this);
- }
- return getLastTextureImpl();
- }
- protected abstract TextureSequence.TextureFrame getLastTextureImpl();
-
+ public final void setTextureWrapST(final int[] wrapST) { texWrapST[0] = wrapST[0]; texWrapST[1] = wrapST[1];}
@Override
- public final synchronized TextureSequence.TextureFrame getNextTexture(GL gl, boolean blocking) throws IllegalStateException {
- if(State.Uninitialized == state) {
- throw new IllegalStateException("Instance not initialized: "+this);
- }
- if(State.Playing == state) {
- final TextureSequence.TextureFrame f = getNextTextureImpl(gl, blocking);
- return f;
+ public final int[] getTextureWrapST() { return texWrapST; }
+
+ private final void checkGLInit() {
+ if(State.Uninitialized == state || State.Initialized == state ) {
+ throw new IllegalStateException("GL not initialized: "+this);
}
- return getLastTextureImpl();
}
- protected abstract TextureSequence.TextureFrame getNextTextureImpl(GL gl, boolean blocking);
-
+
@Override
public String getRequiredExtensionsShaderStub() throws IllegalStateException {
- if(State.Uninitialized == state) {
- throw new IllegalStateException("Instance not initialized: "+this);
- }
+ checkGLInit();
if(GLES2.GL_TEXTURE_EXTERNAL_OES == textureTarget) {
- return TextureSequence.GL_OES_EGL_image_external_Required_Prelude;
+ return ShaderCode.createExtensionDirective(GLExtensions.OES_EGL_image_external, ShaderCode.ENABLE);
}
return "";
}
-
+
@Override
public String getTextureSampler2DType() throws IllegalStateException {
- if(State.Uninitialized == state) {
- throw new IllegalStateException("Instance not initialized: "+this);
- }
+ checkGLInit();
switch(textureTarget) {
case GL.GL_TEXTURE_2D:
- case GL2.GL_TEXTURE_RECTANGLE:
+ case GL2GL3.GL_TEXTURE_RECTANGLE:
return TextureSequence.sampler2D;
case GLES2.GL_TEXTURE_EXTERNAL_OES:
return TextureSequence.samplerExternalOES;
default:
- throw new GLException("Unsuported texture target: "+toHexString(textureTarget));
+ throw new GLException("Unsuported texture target: "+toHexString(textureTarget));
}
}
-
+
/**
* {@inheritDoc}
- *
+ *
* This implementation simply returns the build-in function name of <code>texture2D</code>,
* if not overridden by specialization.
*/
@Override
- public String getTextureLookupFunctionName(String desiredFuncName) throws IllegalStateException {
- if(State.Uninitialized == state) {
- throw new IllegalStateException("Instance not initialized: "+this);
- }
+ public String getTextureLookupFunctionName(final String desiredFuncName) throws IllegalStateException {
+ checkGLInit();
return "texture2D";
}
-
+
/**
* {@inheritDoc}
- *
- * This implementation simply returns an empty string since it's using
+ *
+ * This implementation simply returns an empty string since it's using
* the build-in function <code>texture2D</code>,
* if not overridden by specialization.
*/
@Override
public String getTextureLookupFragmentShaderImpl() throws IllegalStateException {
- if(State.Uninitialized == state) {
- throw new IllegalStateException("Instance not initialized: "+this);
- }
- return "";
+ checkGLInit();
+ return "";
}
-
+
@Override
- public final synchronized float getPlaySpeed() {
- return playSpeed;
+ public final int getTextureFragmentShaderHashCode() {
+ if( !isTextureAvailable() ) {
+ textureFragmentShaderHashCode = 0;
+ return 0;
+ } else if( 0 == textureFragmentShaderHashCode ) {
+ int hash = 31 + getTextureLookupFragmentShaderImpl().hashCode();
+ hash = ((hash << 5) - hash) + getTextureSampler2DType().hashCode();
+ textureFragmentShaderHashCode = hash;
+ }
+ return textureFragmentShaderHashCode;
}
-
+
@Override
- public final synchronized void setPlaySpeed(float rate) {
- if(State.Uninitialized != state && setPlaySpeedImpl(rate)) {
- playSpeed = rate;
+ public final int getDecodedFrameCount() { return decodedFrameCount; }
+
+ @Override
+ public final int getPresentedFrameCount() { return presentedFrameCount; }
+
+ @Override
+ public final int getVideoPTS() { return video_pts_last; }
+
+ @Override
+ public final int getAudioPTS() {
+ if( State.Uninitialized != state ) {
+ return getAudioPTSImpl();
}
- if(DEBUG) { System.err.println("SetPlaySpeed: "+toString()); }
+ return 0;
}
- protected abstract boolean setPlaySpeedImpl(float rate);
-
- public final State start() {
- switch(state) {
- case Stopped:
- case Paused:
- if(startImpl()) {
- state = State.Playing;
- }
+ /** Override if not using audioSink! */
+ protected int getAudioPTSImpl() {
+ if( null != audioSink ) {
+ return audioSink.getPTS();
+ } else {
+ return 0;
}
- if(DEBUG) { System.err.println("Start: "+toString()); }
- return state;
}
- protected abstract boolean startImpl();
-
- public final State pause() {
- if(State.Playing == state && pauseImpl()) {
- state = State.Paused;
+
+ @Override
+ public final State getState() { return state; }
+
+ protected final void setState(final State s) { state=s; }
+
+ @Override
+ public final State play() {
+ synchronized( stateLock ) {
+ final State preState = state;
+ switch( state ) {
+ case Paused:
+ if( playImpl() ) {
+ resetAVPTS();
+ if( null != audioSink ) {
+ audioSink.play(); // cont. w/ new data
+ }
+ if( null != streamWorker ) {
+ streamWorker.doResume();
+ }
+ changeState(0, State.Playing);
+ }
+ default:
+ }
+ if(DEBUG) { System.err.println("Play: "+preState+" -> "+state+", "+toString()); }
+ return state;
}
- if(DEBUG) { System.err.println("Pause: "+toString()); }
- return state;
}
- protected abstract boolean pauseImpl();
-
- public final State stop() {
- switch(state) {
- case Playing:
- case Paused:
- if(stopImpl()) {
- state = State.Stopped;
+ protected abstract boolean playImpl();
+
+ @Override
+ public final State pause(final boolean flush) {
+ return pauseImpl(flush, 0);
+ }
+ private final State pauseImpl(final boolean flush, int event_mask) {
+ synchronized( stateLock ) {
+ final State preState = state;
+ if( State.Playing == state ) {
+ event_mask = addStateEventMask(event_mask, GLMediaPlayer.State.Paused);
+ setState( State.Paused );
+ if( null != streamWorker ) {
+ streamWorker.doPause();
}
+ if( flush ) {
+ resetAVPTSAndFlush();
+ } else if( null != audioSink ) {
+ audioSink.pause();
+ }
+ attributesUpdated( event_mask );
+ if( !pauseImpl() ) {
+ play();
+ }
+ }
+ if(DEBUG) { System.err.println("Pause: "+preState+" -> "+state+", "+toString()); }
+ return state;
}
- if(DEBUG) { System.err.println("Stop: "+toString()); }
- return state;
}
- protected abstract boolean stopImpl();
-
+ protected abstract boolean pauseImpl();
+
@Override
- public final int getCurrentPosition() {
- if(State.Uninitialized != state) {
- return getCurrentPositionImpl();
+ public final State destroy(final GL gl) {
+ return destroyImpl(gl, 0);
+ }
+ private final State destroyImpl(final GL gl, final int event_mask) {
+ synchronized( stateLock ) {
+ if( null != streamWorker ) {
+ streamWorker.doStop();
+ streamWorker = null;
+ }
+ destroyImpl(gl);
+ removeAllTextureFrames(gl);
+ textureCount=0;
+ changeState(event_mask, State.Uninitialized);
+ attachedObjects.clear();
+ return state;
}
- return 0;
}
- protected abstract int getCurrentPositionImpl();
-
+ protected abstract void destroyImpl(GL gl);
+
+ @Override
public final int seek(int msec) {
- final int cp;
- switch(state) {
- case Stopped:
- case Playing:
- case Paused:
- cp = seekImpl(msec);
- break;
- default:
- cp = 0;
+ synchronized( stateLock ) {
+ final State preState = state;
+ final int pts1;
+ switch(state) {
+ case Playing:
+ case Paused:
+ final State _state = state;
+ setState( State.Paused );
+ if( null != streamWorker ) {
+ streamWorker.doPause();
+ }
+ // Adjust target ..
+ if( msec >= duration ) {
+ msec = duration - (int)Math.floor(frame_duration);
+ } else if( msec < 0 ) {
+ msec = 0;
+ }
+ pts1 = seekImpl(msec);
+ resetAVPTSAndFlush();
+ if( null != audioSink && State.Playing == _state ) {
+ audioSink.play(); // cont. w/ new data
+ }
+ if(DEBUG) {
+ System.err.println("Seek("+msec+"): "+getPerfString());
+ }
+ if( null != streamWorker ) {
+ streamWorker.doResume();
+ }
+ setState( _state );
+ break;
+ default:
+ pts1 = 0;
+ }
+ if(DEBUG) { System.err.println("Seek("+msec+"): "+preState+" -> "+state+", "+toString()); }
+ return pts1;
}
- if(DEBUG) { System.err.println("Seek("+msec+"): "+toString()); }
- return cp;
}
protected abstract int seekImpl(int msec);
-
- public final State getState() { return state; }
-
- @Override
- public final State initGLStream(GL gl, URLConnection urlConn) throws IllegalStateException, GLException, IOException {
- if(State.Uninitialized != state) {
- throw new IllegalStateException("Instance not in state "+State.Uninitialized+", but "+state+", "+this);
- }
- this.urlConn = urlConn;
- if (this.urlConn != null) {
- try {
- if(null != gl) {
- if(null!=texFrames) {
- // re-init ..
- removeAllImageTextures(gl);
- } else {
- texFrames = new TextureSequence.TextureFrame[textureCount];
- }
- final int[] tex = new int[textureCount];
- {
- gl.glGenTextures(textureCount, tex, 0);
- final int err = gl.glGetError();
- if( GL.GL_NO_ERROR != err ) {
- throw new RuntimeException("TextureNames creation failed (num: "+textureCount+"): err "+toHexString(err));
- }
+
+ @Override
+ public final float getPlaySpeed() { return playSpeed; }
+
+ @Override
+ public final boolean setPlaySpeed(float rate) {
+ synchronized( stateLock ) {
+ final float preSpeed = playSpeed;
+ boolean res = false;
+ if(State.Uninitialized != state ) {
+ if( rate > 0.01f ) {
+ if( Math.abs(1.0f - rate) < 0.01f ) {
+ rate = 1.0f;
}
- initGLStreamImpl(gl, tex);
-
- for(int i=0; i<textureCount; i++) {
- final TextureSequence.TextureFrame tf = createTexImage(gl, i, tex);
- texFrames[i] = tf;
- texFrameMap.put(tex[i], tf);
+ if( setPlaySpeedImpl(rate) ) {
+ resetAVPTS();
+ playSpeed = rate;
+ res = true;
}
}
- state = State.Stopped;
- return state;
- } catch (Throwable t) {
- throw new GLException("Error initializing GL resources", t);
}
+ if(DEBUG) { System.err.println("setPlaySpeed("+rate+"): "+state+", "+preSpeed+" -> "+playSpeed+", "+toString()); }
+ return res;
+ }
+ }
+ /**
+ * Override if not using AudioSink, or AudioSink's {@link AudioSink#setPlaySpeed(float)} is not sufficient!
+ * <p>
+ * AudioSink shall respect <code>!audioSinkPlaySpeedSet</code> to determine data_size
+ * at {@link AudioSink#enqueueData(com.jogamp.opengl.util.av.AudioSink.AudioFrame)}.
+ * </p>
+ */
+ protected boolean setPlaySpeedImpl(final float rate) {
+ if( null != audioSink ) {
+ audioSinkPlaySpeedSet = audioSink.setPlaySpeed(rate);
}
- return state;
+ // still true, even if audioSink rejects command since we deal w/ video sync
+ // and AudioSink w/ audioSinkPlaySpeedSet at enqueueData(..).
+ return true;
+ }
+
+ @Override
+ public final float getAudioVolume() {
+ getAudioVolumeImpl();
+ return audioVolume;
}
-
/**
- * Implementation shall set the following set of data here
- * @param gl TODO
- * @param texNames TODO
+ * Override if not using AudioSink, or AudioSink's {@link AudioSink#getVolume()} is not sufficient!
+ */
+ protected void getAudioVolumeImpl() {
+ if( null != audioSink ) {
+ audioVolume = audioSink.getVolume();
+ }
+ }
+
+ @Override
+ public boolean setAudioVolume(float v) {
+ synchronized( stateLock ) {
+ final float preVolume = audioVolume;
+ boolean res = false;
+ if(State.Uninitialized != state ) {
+ if( Math.abs(v) < 0.01f ) {
+ v = 0.0f;
+ } else if( Math.abs(1.0f - v) < 0.01f ) {
+ v = 1.0f;
+ }
+ if( setAudioVolumeImpl(v) ) {
+ audioVolume = v;
+ res = true;
+ }
+ }
+ if(DEBUG) { System.err.println("setAudioVolume("+v+"): "+state+", "+preVolume+" -> "+audioVolume+", "+toString()); }
+ return res;
+ }
+ }
+ /**
+ * Override if not using AudioSink, or AudioSink's {@link AudioSink#setVolume(float)} is not sufficient!
+ */
+ protected boolean setAudioVolumeImpl(final float v) {
+ if( null != audioSink ) {
+ return audioSink.setVolume(v);
+ }
+ // still true, even if audioSink rejects command ..
+ return true;
+ }
+
+ @Override
+ public final void initStream(final Uri streamLoc, final int vid, final int aid, final int reqTextureCount) throws IllegalStateException, IllegalArgumentException {
+ synchronized( stateLock ) {
+ if(State.Uninitialized != state) {
+ throw new IllegalStateException("Instance not in state unintialized: "+this);
+ }
+ if(null == streamLoc) {
+ throw new IllegalArgumentException("streamLock is null");
+ }
+ if( STREAM_ID_NONE != vid ) {
+ textureCount = validateTextureCount(reqTextureCount);
+ if( textureCount < TEXTURE_COUNT_MIN ) {
+ throw new InternalError("Validated texture count < "+TEXTURE_COUNT_MIN+": "+textureCount);
+ }
+ } else {
+ textureCount = 0;
+ }
+ decodedFrameCount = 0;
+ presentedFrameCount = 0;
+ displayedFrameCount = 0;
+ nullFrameCount = 0;
+ maxNullFrameCountUntilEOS = MAX_FRAMELESS_UNTIL_EOS_DEFAULT;
+ this.streamLoc = streamLoc;
+
+ // Pre-parse for camera-input scheme
+ cameraPath = null;
+ cameraProps = null;
+ final Uri.Encoded streamLocScheme = streamLoc.scheme;
+ if( null != streamLocScheme && streamLocScheme.equals(CameraInputScheme) ) {
+ final Uri.Encoded rawPath = streamLoc.path;
+ if( null != rawPath && rawPath.length() > 0 ) {
+ // cut-off root fwd-slash
+ cameraPath = rawPath.substring(1);
+ final UriQueryProps props = UriQueryProps.create(streamLoc, ';');
+ cameraProps = props.getProperties();
+ } else {
+ throw new IllegalArgumentException("Camera path is empty: "+streamLoc.toString());
+ }
+ }
+
+ this.vid = vid;
+ this.aid = aid;
+ new Thread() {
+ public void run() {
+ try {
+ // StreamWorker may be used, see API-doc of StreamWorker
+ initStreamImpl(vid, aid);
+ } catch (final Throwable t) {
+ streamErr = new StreamException(t.getClass().getSimpleName()+" while initializing: "+GLMediaPlayerImpl.this.toString(), t);
+ changeState(GLMediaEventListener.EVENT_CHANGE_ERR, GLMediaPlayer.State.Uninitialized);
+ } // also initializes width, height, .. etc
+ }
+ }.start();
+ }
+ }
+ /**
+ * Implementation shall set the following set of data here
+ * @see #vid
+ * @see #aid
* @see #width
* @see #height
* @see #fps
* @see #bps_stream
- * @see #totalFrames
+ * @see #videoFrames
+ * @see #audioFrames
* @see #acodec
* @see #vcodec
*/
- protected abstract void initGLStreamImpl(GL gl, int[] texNames) throws IOException;
-
- protected TextureSequence.TextureFrame createTexImage(GL gl, int idx, int[] tex) {
- return new TextureSequence.TextureFrame( createTexImageImpl(gl, idx, tex, width, height, false) );
+ protected abstract void initStreamImpl(int vid, int aid) throws Exception;
+
+ @Override
+ public final StreamException getStreamException() {
+ final StreamException e;
+ synchronized( stateLock ) {
+ e = streamErr;
+ streamErr = null;
+ }
+ return e;
+ }
+
+ @Override
+ public final void initGL(final GL gl) throws IllegalStateException, StreamException, GLException {
+ synchronized( stateLock ) {
+ if(State.Initialized != state ) {
+ throw new IllegalStateException("Stream not in state initialized: "+this);
+ }
+ if( null != streamWorker ) {
+ final StreamException streamInitErr = getStreamException();
+ if( null != streamInitErr ) {
+ streamWorker = null; // already terminated!
+ destroy(null);
+ throw streamInitErr;
+ }
+ }
+ try {
+ if( STREAM_ID_NONE != vid ) {
+ removeAllTextureFrames(gl);
+ initGLImpl(gl);
+ if(DEBUG) {
+ System.err.println("initGLImpl.X "+this);
+ }
+ videoFramesOrig = createTexFrames(gl, textureCount);
+ if( TEXTURE_COUNT_MIN == textureCount ) {
+ videoFramesFree = null;
+ videoFramesDecoded = null;
+ lastFrame = videoFramesOrig[0];
+ } else {
+ videoFramesFree = new LFRingbuffer<TextureFrame>(videoFramesOrig);
+ videoFramesDecoded = new LFRingbuffer<TextureFrame>(TextureFrame[].class, textureCount);
+ lastFrame = videoFramesFree.getBlocking( );
+ }
+ if( null != streamWorker ) {
+ streamWorker.initGL(gl);
+ }
+ } else {
+ removeAllTextureFrames(null);
+ initGLImpl(null);
+ setTextureFormat(-1, -1);
+ setTextureType(-1);
+ videoFramesOrig = null;
+ videoFramesFree = null;
+ videoFramesDecoded = null;
+ lastFrame = null;
+ }
+ changeState(0, State.Paused);
+ } catch (final Throwable t) {
+ destroyImpl(gl, GLMediaEventListener.EVENT_CHANGE_ERR); // -> GLMediaPlayer.State.Uninitialized
+ throw new GLException("Error initializing GL resources", t);
+ }
+ }
+ }
+ /**
+ * Shall initialize all GL related resources, if not audio-only.
+ * <p>
+ * Shall also take care of {@link AudioSink} initialization if appropriate.
+ * </p>
+ * @param gl null for audio-only, otherwise a valid and current GL object.
+ * @throws IOException
+ * @throws GLException
+ */
+ protected abstract void initGLImpl(GL gl) throws IOException, GLException;
+
+ /**
+ * Returns the validated number of textures to be handled.
+ * <p>
+ * Default is {@link #TEXTURE_COUNT_DEFAULT} minimum textures, if <code>desiredTextureCount</code>
+ * is < {@link #TEXTURE_COUNT_MIN}, {@link #TEXTURE_COUNT_MIN} is returned.
+ * </p>
+ * <p>
+ * Implementation must at least return a texture count of {@link #TEXTURE_COUNT_MIN}, <i>two</i>, the last texture and the decoding texture.
+ * </p>
+ */
+ protected int validateTextureCount(final int desiredTextureCount) {
+ return desiredTextureCount < TEXTURE_COUNT_MIN ? TEXTURE_COUNT_MIN : desiredTextureCount;
+ }
+
+ protected TextureFrame[] createTexFrames(final GL gl, final int count) {
+ final int[] texNames = new int[count];
+ gl.glGenTextures(count, texNames, 0);
+ final int err = gl.glGetError();
+ if( GL.GL_NO_ERROR != err ) {
+ throw new RuntimeException("TextureNames creation failed (num: "+count+"): err "+toHexString(err));
+ }
+ final TextureFrame[] texFrames = new TextureFrame[count];
+ for(int i=0; i<count; i++) {
+ texFrames[i] = createTexImage(gl, texNames[i]);
+ }
+ return texFrames;
}
-
- protected Texture createTexImageImpl(GL gl, int idx, int[] tex, int tWidth, int tHeight, boolean mustFlipVertically) {
- if( 0 > tex[idx] ) {
- throw new RuntimeException("TextureName "+toHexString(tex[idx])+" invalid.");
+ protected abstract TextureFrame createTexImage(GL gl, int texName);
+
+ protected final Texture createTexImageImpl(final GL gl, final int texName, final int tWidth, final int tHeight) {
+ if( 0 > texName ) {
+ throw new RuntimeException("TextureName "+toHexString(texName)+" invalid.");
}
gl.glActiveTexture(GL.GL_TEXTURE0+getTextureUnit());
- gl.glBindTexture(textureTarget, tex[idx]);
+ gl.glBindTexture(textureTarget, texName);
{
final int err = gl.glGetError();
if( GL.GL_NO_ERROR != err ) {
- throw new RuntimeException("Couldn't bind textureName "+toHexString(tex[idx])+" to 2D target, err "+toHexString(err));
+ throw new RuntimeException("Couldn't bind textureName "+toHexString(texName)+" to 2D target, err "+toHexString(err));
}
}
@@ -376,55 +728,696 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
{
final int err = gl.glGetError();
if( GL.GL_NO_ERROR != err ) {
- throw new RuntimeException("Couldn't create TexImage2D RGBA "+tWidth+"x"+tHeight+", err "+toHexString(err));
+ throw new RuntimeException("Couldn't create TexImage2D RGBA "+tWidth+"x"+tHeight+", target "+toHexString(textureTarget)+
+ ", ifmt "+toHexString(textureInternalFormat)+", fmt "+toHexString(textureFormat)+", type "+toHexString(textureType)+
+ ", err "+toHexString(err));
}
}
if(DEBUG) {
System.err.println("Created TexImage2D RGBA "+tWidth+"x"+tHeight+", target "+toHexString(textureTarget)+
- ", ifmt "+toHexString(GL.GL_RGBA)+", fmt "+toHexString(textureFormat)+", type "+toHexString(textureType));
+ ", ifmt "+toHexString(textureInternalFormat)+", fmt "+toHexString(textureFormat)+", type "+toHexString(textureType));
}
}
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MIN_FILTER, texMinMagFilter[0]);
- gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MAG_FILTER, texMinMagFilter[1]);
+ gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MAG_FILTER, texMinMagFilter[1]);
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_S, texWrapST[0]);
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_T, texWrapST[1]);
-
- return com.jogamp.opengl.util.texture.TextureIO.newTexture(tex[idx],
- textureTarget,
- tWidth, tHeight,
- width, height,
- mustFlipVertically);
- }
-
- protected void destroyTexImage(GL gl, TextureSequence.TextureFrame imgTex) {
- imgTex.getTexture().destroy(gl);
- }
-
- protected void removeAllImageTextures(GL gl) {
- if(null != texFrames) {
- for(int i=0; i<textureCount; i++) {
- final TextureSequence.TextureFrame imgTex = texFrames[i];
- if(null != imgTex) {
- destroyTexImage(gl, imgTex);
+
+ return new Texture(texName, textureTarget,
+ tWidth, tHeight, width, height, !isInGLOrientation);
+ }
+
+ protected void destroyTexFrame(final GL gl, final TextureFrame frame) {
+ frame.getTexture().destroy(gl);
+ }
+
+ @Override
+ public final boolean isTextureAvailable() {
+ return State.Paused == state || State.Playing == state;
+ }
+
+ @Override
+ public final TextureFrame getLastTexture() throws IllegalStateException {
+ if( State.Paused != state && State.Playing != state ) {
+ throw new IllegalStateException("Instance not paused or playing: "+this);
+ }
+ return lastFrame;
+ }
+
+ private final void removeAllTextureFrames(final GL gl) {
+ final TextureFrame[] texFrames = videoFramesOrig;
+ videoFramesOrig = null;
+ videoFramesFree = null;
+ videoFramesDecoded = null;
+ lastFrame = null;
+ if( null != texFrames ) {
+ for(int i=0; i<texFrames.length; i++) {
+ final TextureFrame frame = texFrames[i];
+ if(null != frame) {
+ if( null != gl ) {
+ destroyTexFrame(gl, frame);
+ }
texFrames[i] = null;
}
+ if( DEBUG ) {
+ System.err.println(Thread.currentThread().getName()+"> Clear TexFrame["+i+"]: "+frame+" -> null");
+ }
}
}
- texFrameMap.clear();
}
- protected final void updateAttributes(int width, int height, int bps_stream, int bps_video, int bps_audio,
- float fps, int totalFrames, int duration,
- String vcodec, String acodec) {
+ private TextureFrame cachedFrame = null;
+ private long lastTimeMillis = 0;
+
+ private final boolean[] stGotVFrame = { false };
+
+ @Override
+ public final TextureFrame getNextTexture(final GL gl) throws IllegalStateException {
+ synchronized( stateLock ) {
+ if( State.Paused != state && State.Playing != state ) {
+ throw new IllegalStateException("Instance not paused or playing: "+this);
+ }
+ if(State.Playing == state) {
+ boolean dropFrame = false;
+ try {
+ do {
+ final boolean droppedFrame;
+ if( dropFrame ) {
+ presentedFrameCount--;
+ dropFrame = false;
+ droppedFrame = true;
+ } else {
+ droppedFrame = false;
+ }
+ final boolean playCached = null != cachedFrame;
+ final int video_pts;
+ final boolean hasVideoFrame;
+ TextureFrame nextFrame;
+ if( playCached ) {
+ nextFrame = cachedFrame;
+ cachedFrame = null;
+ presentedFrameCount--;
+ video_pts = nextFrame.getPTS();
+ hasVideoFrame = true;
+ } else {
+ if( null != videoFramesDecoded ) {
+ // multi-threaded and video available
+ nextFrame = videoFramesDecoded.get();
+ if( null != nextFrame ) {
+ video_pts = nextFrame.getPTS();
+ hasVideoFrame = true;
+ } else {
+ video_pts = TimeFrameI.INVALID_PTS;
+ hasVideoFrame = false;
+ }
+ } else {
+ // single-threaded or audio-only
+ video_pts = getNextSingleThreaded(gl, lastFrame, stGotVFrame);
+ nextFrame = lastFrame;
+ hasVideoFrame = stGotVFrame[0];
+ }
+ }
+ final long currentTimeMillis = Platform.currentTimeMillis();
+
+ if( TimeFrameI.END_OF_STREAM_PTS == video_pts ||
+ ( duration > 0 && duration <= video_pts ) || maxNullFrameCountUntilEOS <= nullFrameCount )
+ {
+ // EOS
+ if( DEBUG ) {
+ System.err.println( "AV-EOS (getNextTexture): EOS_PTS "+(TimeFrameI.END_OF_STREAM_PTS == video_pts)+", "+this);
+ }
+ pauseImpl(true, GLMediaEventListener.EVENT_CHANGE_EOS);
+
+ } else if( TimeFrameI.INVALID_PTS == video_pts ) { // no audio or video frame
+ if( null == videoFramesDecoded || !videoFramesDecoded.isEmpty() ) {
+ nullFrameCount++;
+ }
+ if( DEBUG ) {
+ final int audio_pts = getAudioPTSImpl();
+ final int audio_scr = (int) ( ( currentTimeMillis - audio_scr_t0 ) * playSpeed );
+ final int d_apts;
+ if( audio_pts != TimeFrameI.INVALID_PTS ) {
+ d_apts = audio_pts - audio_scr;
+ } else {
+ d_apts = 0;
+ }
+ final int video_scr = video_scr_pts + (int) ( ( currentTimeMillis - video_scr_t0 ) * playSpeed );
+ final int d_vpts = video_pts - video_scr;
+ System.err.println( "AV~: dT "+(currentTimeMillis-lastTimeMillis)+", nullFrames "+nullFrameCount+
+ getPerfStringImpl( video_scr, video_pts, d_vpts, audio_scr, audio_pts, d_apts, 0 ) + ", droppedFrame "+droppedFrame);
+ }
+ } else { // valid pts: has audio or video frame
+ nullFrameCount=0;
+
+ if( hasVideoFrame ) { // has video frame
+ presentedFrameCount++;
+
+ final int audio_pts = getAudioPTSImpl();
+ final int audio_scr = (int) ( ( currentTimeMillis - audio_scr_t0 ) * playSpeed );
+ final int d_apts;
+ if( audio_pts != TimeFrameI.INVALID_PTS ) {
+ d_apts = audio_pts - audio_scr;
+ } else {
+ d_apts = 0;
+ }
+
+ final int frame_period_last = video_pts - video_pts_last; // rendering loop interrupted ?
+ if( videoSCR_reset || frame_period_last > frame_duration*10 ) {
+ videoSCR_reset = false;
+ video_scr_t0 = currentTimeMillis;
+ video_scr_pts = video_pts;
+ }
+ final int video_scr = video_scr_pts + (int) ( ( currentTimeMillis - video_scr_t0 ) * playSpeed );
+ final int d_vpts = video_pts - video_scr;
+ // final int d_avpts = d_vpts - d_apts;
+ if( -VIDEO_DPTS_MAX > d_vpts || d_vpts > VIDEO_DPTS_MAX ) {
+ // if( -VIDEO_DPTS_MAX > d_avpts || d_avpts > VIDEO_DPTS_MAX ) {
+ if( DEBUG ) {
+ System.err.println( "AV*: dT "+(currentTimeMillis-lastTimeMillis)+", "+
+ getPerfStringImpl( video_scr, video_pts, d_vpts, audio_scr, audio_pts, d_apts, 0 ) + ", "+nextFrame+", playCached " + playCached+ ", dropFrame "+dropFrame);
+ }
+ } else {
+ final int dpy_den = displayedFrameCount > 0 ? displayedFrameCount : 1;
+ final int avg_dpy_duration = ( (int) ( currentTimeMillis - video_scr_t0 ) ) / dpy_den ; // ms/f
+ final int maxVideoDelay = Math.min(avg_dpy_duration, MAXIMUM_VIDEO_ASYNC);
+ video_dpts_count++;
+ // video_dpts_cum = d_avpts + VIDEO_DPTS_COEFF * video_dpts_cum;
+ video_dpts_cum = d_vpts + VIDEO_DPTS_COEFF * video_dpts_cum;
+ final int video_dpts_avg_diff = video_dpts_count >= VIDEO_DPTS_NUM ? getVideoDPTSAvg() : 0;
+ final int dt = (int) ( video_dpts_avg_diff / playSpeed + 0.5f );
+ // final int dt = (int) ( d_vpts / playSpeed + 0.5f );
+ // final int dt = (int) ( d_avpts / playSpeed + 0.5f );
+ final TextureFrame _nextFrame = nextFrame;
+ if( dt > maxVideoDelay ) {
+ cachedFrame = nextFrame;
+ nextFrame = null;
+ } else if ( !droppedFrame && dt < -maxVideoDelay && null != videoFramesDecoded && videoFramesDecoded.size() > 0 ) {
+ // only drop if prev. frame has not been dropped and
+ // frame is too late and one decoded frame is already available.
+ dropFrame = true;
+ }
+ video_pts_last = video_pts;
+ if( DEBUG ) {
+ System.err.println( "AV_: dT "+(currentTimeMillis-lastTimeMillis)+", "+
+ getPerfStringImpl( video_scr, video_pts, d_vpts,
+ audio_scr, audio_pts, d_apts,
+ video_dpts_avg_diff ) +
+ ", avg dpy-fps "+avg_dpy_duration+" ms/f, maxD "+maxVideoDelay+" ms, "+_nextFrame+", playCached " + playCached + ", dropFrame "+dropFrame);
+ }
+ }
+ } // has video frame
+ } // has audio or video frame
+
+ if( null != videoFramesFree && null != nextFrame ) {
+ // Had frame and not single threaded ? (TEXTURE_COUNT_MIN < textureCount)
+ final TextureFrame _lastFrame = lastFrame;
+ lastFrame = nextFrame;
+ if( null != _lastFrame ) {
+ videoFramesFree.putBlocking(_lastFrame);
+ }
+ }
+ lastTimeMillis = currentTimeMillis;
+ } while( dropFrame );
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ displayedFrameCount++;
+ return lastFrame;
+ }
+ }
+ protected void preNextTextureImpl(final GL gl) {}
+ protected void postNextTextureImpl(final GL gl) {}
+ /**
+ * Process stream until the next video frame, i.e. {@link TextureFrame}, has been reached.
+ * Audio frames, i.e. {@link AudioSink.AudioFrame}, shall be handled in the process.
+ * <p>
+ * Video frames shall be ignored, if {@link #getVID()} is {@link #STREAM_ID_NONE}.
+ * </p>
+ * <p>
+ * Audio frames shall be ignored, if {@link #getAID()} is {@link #STREAM_ID_NONE}.
+ * </p>
+ * <p>
+ * Method may be invoked on the <a href="#streamworker"><i>StreamWorker</i> decoding thread</a>.
+ * </p>
+ * <p>
+ * Implementation shall care of OpenGL synchronization as required, e.g. glFinish()/glFlush()!
+ * </p>
+ * @param gl valid and current GL instance, shall be <code>null</code> for audio only.
+ * @param nextFrame the {@link TextureFrame} to store the video PTS and texture data,
+ * shall be <code>null</code> for audio only.
+ * @return the last processed video PTS value, maybe {@link TimeFrameI#INVALID_PTS} if video frame is invalid or n/a.
+ * Will be {@link TimeFrameI#END_OF_STREAM_PTS} if end of stream reached.
+ */
+ protected abstract int getNextTextureImpl(GL gl, TextureFrame nextFrame);
+
+ protected final int getNextSingleThreaded(final GL gl, final TextureFrame nextFrame, final boolean[] gotVFrame) throws InterruptedException {
+ final int pts;
+ if( STREAM_ID_NONE != vid ) {
+ preNextTextureImpl(gl);
+ pts = getNextTextureImpl(gl, nextFrame);
+ postNextTextureImpl(gl);
+ if( TimeFrameI.INVALID_PTS != pts ) {
+ newFrameAvailable(nextFrame, Platform.currentTimeMillis());
+ gotVFrame[0] = true;
+ } else {
+ gotVFrame[0] = false;
+ }
+ } else {
+ // audio only
+ pts = getNextTextureImpl(null, null);
+ gotVFrame[0] = false;
+ }
+ return pts;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Note: All {@link AudioSink} operations are performed from {@link GLMediaPlayerImpl},
+ * i.e. {@link #play()}, {@link #pause(boolean)}, {@link #seek(int)}, {@link #setPlaySpeed(float)}, {@link #getAudioPTS()}.
+ * </p>
+ * <p>
+ * Implementations using an {@link AudioSink} shall write it's instance to {@link #audioSink}
+ * from within their {@link #initStreamImpl(int, int)} implementation.
+ * </p>
+ */
+ @Override
+ public final AudioSink getAudioSink() { return audioSink; }
+
+ /**
+ * To be called from implementation at 1st PTS after start
+ * w/ current pts value in milliseconds.
+ * @param audio_scr_t0
+ */
+ protected void setFirstAudioPTS2SCR(final int pts) {
+ if( audioSCR_reset ) {
+ audio_scr_t0 = Platform.currentTimeMillis() - pts;
+ audioSCR_reset = false;
+ }
+ }
+ private void flushAllVideoFrames() {
+ if( null != videoFramesFree ) {
+ videoFramesFree.resetFull(videoFramesOrig);
+ lastFrame = videoFramesFree.get();
+ if( null == lastFrame ) { throw new InternalError("XXX"); }
+ videoFramesDecoded.clear();
+ }
+ cachedFrame = null;
+ }
+ private void resetAVPTSAndFlush() {
+ video_dpts_cum = 0;
+ video_dpts_count = 0;
+ resetAVPTS();
+ flushAllVideoFrames();
+ if( null != audioSink ) {
+ audioSink.flush();
+ }
+ }
+ private void resetAVPTS() {
+ nullFrameCount = 0;
+ presentedFrameCount = 0;
+ displayedFrameCount = 0;
+ decodedFrameCount = 0;
+ audioSCR_reset = true;
+ videoSCR_reset = true;
+ }
+ private final int getVideoDPTSAvg() {
+ return (int) ( video_dpts_cum * (1.0f - VIDEO_DPTS_COEFF) + 0.5f );
+ }
+
+ private final void newFrameAvailable(final TextureFrame frame, final long currentTimeMillis) {
+ decodedFrameCount++; // safe: only written-to either from stream-worker or user thread
+ if( 0 == frame.getDuration() ) { // patch frame duration if not set already
+ frame.setDuration( (int) frame_duration );
+ }
+ synchronized(eventListenersLock) {
+ for(final Iterator<GLMediaEventListener> i = eventListeners.iterator(); i.hasNext(); ) {
+ i.next().newFrameAvailable(this, frame, currentTimeMillis);
+ }
+ }
+ }
+
+ /**
+ * After {@link GLMediaPlayerImpl#initStreamImpl(int, int) initStreamImpl(..)} is completed via
+ * {@link GLMediaPlayerImpl#updateAttributes(int, int, int, int, int, int, int, float, int, int, int, String, String) updateAttributes(..)},
+ * the latter decides whether StreamWorker is being used.
+ */
+ class StreamWorker extends Thread {
+ private volatile boolean isRunning = false;
+ private volatile boolean isActive = false;
+ private volatile boolean isBlocked = false;
+
+ private volatile boolean shallPause = true;
+ private volatile boolean shallStop = false;
+
+ private volatile GLContext sharedGLCtx = null;
+ private boolean sharedGLCtxCurrent = false;
+ private GLDrawable dummyDrawable = null;
+
+ /**
+ * Starts this daemon thread,
+ * <p>
+ * This thread pauses after it's started!
+ * </p>
+ **/
+ StreamWorker() {
+ setDaemon(true);
+ synchronized(this) {
+ start();
+ while( !isRunning ) {
+ this.notifyAll(); // wake-up startup-block
+ try {
+ this.wait(); // wait until started
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private void makeCurrent(final GLContext ctx) {
+ if( GLContext.CONTEXT_NOT_CURRENT >= ctx.makeCurrent() ) {
+ throw new GLException("Couldn't make ctx current: "+ctx);
+ }
+ }
+
+ private void destroySharedGL() {
+ if( null != sharedGLCtx ) {
+ if( sharedGLCtx.isCreated() ) {
+ // Catch dispose GLExceptions by GLEventListener, just 'print' them
+ // so we can continue with the destruction.
+ try {
+ sharedGLCtx.destroy();
+ } catch (final GLException gle) {
+ gle.printStackTrace();
+ }
+ }
+ sharedGLCtx = null;
+ }
+ if( null != dummyDrawable ) {
+ final AbstractGraphicsDevice device = dummyDrawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice();
+ dummyDrawable.setRealized(false);
+ dummyDrawable = null;
+ device.close();
+ }
+ }
+
+ public final synchronized void initGL(final GL gl) {
+ final GLContext glCtx = gl.getContext();
+ final boolean glCtxCurrent = glCtx.isCurrent();
+ final GLProfile glp = gl.getGLProfile();
+ final GLDrawableFactory factory = GLDrawableFactory.getFactory(glp);
+ final AbstractGraphicsDevice device = glCtx.getGLDrawable().getNativeSurface().getGraphicsConfiguration().getScreen().getDevice();
+ dummyDrawable = factory.createDummyDrawable(device, true, glCtx.getGLDrawable().getChosenGLCapabilities(), null); // own device!
+ dummyDrawable.setRealized(true);
+ sharedGLCtx = dummyDrawable.createContext(glCtx);
+ makeCurrent(sharedGLCtx);
+ if( glCtxCurrent ) {
+ makeCurrent(glCtx);
+ } else {
+ sharedGLCtx.release();
+ }
+ }
+ public final synchronized void doPause() {
+ if( isActive ) {
+ shallPause = true;
+ if( Thread.currentThread() != this ) {
+ if( isBlocked && isActive ) {
+ this.interrupt();
+ }
+ while( isActive && isRunning ) {
+ try {
+ this.wait(); // wait until paused
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ public final synchronized void doResume() {
+ if( isRunning && !isActive ) {
+ shallPause = false;
+ if( Thread.currentThread() != this ) {
+ while( !isActive && !shallPause && isRunning ) {
+ this.notifyAll(); // wake-up pause-block
+ try {
+ this.wait(); // wait until resumed
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ public final synchronized void doStop() {
+ if( isRunning ) {
+ shallStop = true;
+ if( Thread.currentThread() != this ) {
+ if( isBlocked && isRunning ) {
+ this.interrupt();
+ }
+ while( isRunning ) {
+ this.notifyAll(); // wake-up pause-block (opt)
+ try {
+ this.wait(); // wait until stopped
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ public final boolean isRunning() { return isRunning; }
+ public final boolean isActive() { return isActive; }
+
+ @Override
+ public final void run() {
+ setName(getName()+"-StreamWorker_"+StreamWorkerInstanceId);
+ StreamWorkerInstanceId++;
+
+ synchronized ( this ) {
+ isRunning = true;
+ this.notifyAll(); // wake-up ctor()
+ }
+
+ while( !shallStop ){
+ if( shallPause ) {
+ synchronized ( this ) {
+ if( sharedGLCtxCurrent ) {
+ postNextTextureImpl(sharedGLCtx.getGL());
+ sharedGLCtx.release();
+ }
+ while( shallPause && !shallStop ) {
+ isActive = false;
+ this.notifyAll(); // wake-up doPause()
+ try {
+ this.wait(); // wait until resumed
+ } catch (final InterruptedException e) {
+ if( !shallPause ) {
+ e.printStackTrace();
+ }
+ }
+ }
+ if( sharedGLCtxCurrent ) {
+ makeCurrent(sharedGLCtx);
+ preNextTextureImpl(sharedGLCtx.getGL());
+ }
+ isActive = true;
+ this.notifyAll(); // wake-up doResume()
+ }
+ }
+ if( !sharedGLCtxCurrent && null != sharedGLCtx ) {
+ synchronized ( this ) {
+ if( null != sharedGLCtx ) {
+ makeCurrent( sharedGLCtx );
+ preNextTextureImpl(sharedGLCtx.getGL());
+ sharedGLCtxCurrent = true;
+ }
+ if( null == videoFramesFree ) {
+ throw new InternalError("XXX videoFramesFree is null");
+ }
+ }
+ }
+
+ if( !shallStop ) {
+ TextureFrame nextFrame = null;
+ try {
+ isBlocked = true;
+ final GL gl;
+ if( STREAM_ID_NONE != vid ) {
+ nextFrame = videoFramesFree.getBlocking();
+ nextFrame.setPTS( TimeFrameI.INVALID_PTS ); // mark invalid until processed!
+ gl = sharedGLCtx.getGL();
+ } else {
+ gl = null;
+ }
+ isBlocked = false;
+ final int vPTS = getNextTextureImpl(gl, nextFrame);
+ boolean audioEOS = false;
+ if( TimeFrameI.INVALID_PTS != vPTS ) {
+ if( null != nextFrame ) {
+ if( STREAM_WORKER_DELAY > 0 ) {
+ Thread.sleep(STREAM_WORKER_DELAY);
+ }
+ if( !videoFramesDecoded.put(nextFrame) ) {
+ throw new InternalError("XXX: free "+videoFramesFree+", decoded "+videoFramesDecoded+", "+GLMediaPlayerImpl.this);
+ }
+ newFrameAvailable(nextFrame, Platform.currentTimeMillis());
+ nextFrame = null;
+ } else {
+ // audio only
+ if( TimeFrameI.END_OF_STREAM_PTS == vPTS || ( duration > 0 && duration < vPTS ) ) {
+ audioEOS = true;
+ } else {
+ nullFrameCount = 0;
+ }
+ }
+ } else if( null == nextFrame ) {
+ // audio only
+ audioEOS = maxNullFrameCountUntilEOS <= nullFrameCount;
+ if( null == audioSink || 0 == audioSink.getEnqueuedFrameCount() ) {
+ nullFrameCount++;
+ }
+ }
+ if( audioEOS ) {
+ // state transition incl. notification
+ synchronized ( this ) {
+ shallPause = true;
+ isActive = false;
+ this.notifyAll(); // wake-up potential do*()
+ }
+ if( DEBUG ) {
+ System.err.println( "AV-EOS (StreamWorker): EOS_PTS "+(TimeFrameI.END_OF_STREAM_PTS == vPTS)+", "+GLMediaPlayerImpl.this);
+ }
+ pauseImpl(true, GLMediaEventListener.EVENT_CHANGE_EOS);
+ }
+ } catch (final InterruptedException e) {
+ isBlocked = false;
+ if( !shallStop && !shallPause ) {
+ streamErr = new StreamException("InterruptedException while decoding: "+GLMediaPlayerImpl.this.toString(), e);
+ }
+ } catch (final Throwable t) {
+ streamErr = new StreamException(t.getClass().getSimpleName()+" while decoding: "+GLMediaPlayerImpl.this.toString(), t);
+ } finally {
+ if( null != nextFrame ) { // put back
+ videoFramesFree.put(nextFrame);
+ }
+ if( null != streamErr ) {
+ if( DEBUG ) {
+ final Throwable t = null != streamErr.getCause() ? streamErr.getCause() : streamErr;
+ System.err.println("Caught StreamException: "+t.getMessage());
+ t.printStackTrace();
+ }
+ // state transition incl. notification
+ synchronized ( this ) {
+ shallPause = true;
+ isActive = false;
+ this.notifyAll(); // wake-up potential do*()
+ }
+ pauseImpl(true, GLMediaEventListener.EVENT_CHANGE_ERR);
+ }
+ }
+ }
+ }
+ synchronized ( this ) {
+ if( sharedGLCtxCurrent ) {
+ postNextTextureImpl(sharedGLCtx.getGL());
+ }
+ destroySharedGL();
+ isRunning = false;
+ isActive = false;
+ this.notifyAll(); // wake-up doStop()
+ }
+ }
+ }
+ static int StreamWorkerInstanceId = 0;
+ private volatile StreamWorker streamWorker = null;
+ private volatile StreamException streamErr = null;
+
+ protected final int addStateEventMask(int event_mask, final State newState) {
+ if( state != newState ) {
+ switch( newState ) {
+ case Uninitialized:
+ event_mask |= GLMediaEventListener.EVENT_CHANGE_UNINIT;
+ break;
+ case Initialized:
+ event_mask |= GLMediaEventListener.EVENT_CHANGE_INIT;
+ break;
+ case Playing:
+ event_mask |= GLMediaEventListener.EVENT_CHANGE_PLAY;
+ break;
+ case Paused:
+ event_mask |= GLMediaEventListener.EVENT_CHANGE_PAUSE;
+ break;
+ }
+ }
+ return event_mask;
+ }
+
+ protected final void attributesUpdated(final int event_mask) {
+ if( 0 != event_mask ) {
+ final long now = Platform.currentTimeMillis();
+ synchronized(eventListenersLock) {
+ for(final Iterator<GLMediaEventListener> i = eventListeners.iterator(); i.hasNext(); ) {
+ i.next().attributesChanged(this, event_mask, now);
+ }
+ }
+ }
+ }
+
+ protected final void changeState(int event_mask, final State newState) {
+ event_mask = addStateEventMask(event_mask, newState);
+ if( 0 != event_mask ) {
+ setState( newState );
+ if( !isTextureAvailable() ) {
+ textureFragmentShaderHashCode = 0;
+ }
+ attributesUpdated( event_mask );
+ }
+ }
+
+ protected final void updateAttributes(int vid, final int aid, final int width, final int height, final int bps_stream,
+ final int bps_video, final int bps_audio, final float fps,
+ final int videoFrames, final int audioFrames, final int duration, final String vcodec, final String acodec) {
int event_mask = 0;
+ final boolean wasUninitialized = state == State.Uninitialized;
+
+ if( wasUninitialized ) {
+ event_mask |= GLMediaEventListener.EVENT_CHANGE_INIT;
+ setState( State.Initialized );
+ }
+ if( STREAM_ID_AUTO == vid ) {
+ vid = STREAM_ID_NONE;
+ }
+ if( this.vid != vid ) {
+ event_mask |= GLMediaEventListener.EVENT_CHANGE_VID;
+ this.vid = vid;
+ }
+ if( STREAM_ID_AUTO == vid ) {
+ vid = STREAM_ID_NONE;
+ }
+ if( this.aid != aid ) {
+ event_mask |= GLMediaEventListener.EVENT_CHANGE_AID;
+ this.aid = aid;
+ }
if( this.width != width || this.height != height ) {
event_mask |= GLMediaEventListener.EVENT_CHANGE_SIZE;
this.width = width;
this.height = height;
- }
+ }
if( this.fps != fps ) {
event_mask |= GLMediaEventListener.EVENT_CHANGE_FPS;
this.fps = fps;
+ if( 0 != fps ) {
+ this.frame_duration = 1000f / fps;
+ this.maxNullFrameCountUntilEOS = MAX_FRAMELESS_MS_UNTIL_EOS / (int)this.frame_duration;
+ } else {
+ this.frame_duration = 0;
+ this.maxNullFrameCountUntilEOS = MAX_FRAMELESS_UNTIL_EOS_DEFAULT;
+ }
}
if( this.bps_stream != bps_stream || this.bps_video != bps_video || this.bps_audio != bps_audio ) {
event_mask |= GLMediaEventListener.EVENT_CHANGE_BPS;
@@ -432,12 +1425,13 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
this.bps_video = bps_video;
this.bps_audio = bps_audio;
}
- if( this.totalFrames != totalFrames || this.duration != duration ) {
+ if( this.videoFrames != videoFrames || this.audioFrames != audioFrames || this.duration != duration ) {
event_mask |= GLMediaEventListener.EVENT_CHANGE_LENGTH;
- this.totalFrames = totalFrames;
+ this.videoFrames = videoFrames;
+ this.audioFrames = audioFrames;
this.duration = duration;
}
- if( (null!=acodec && acodec.length()>0 && !this.acodec.equals(acodec)) ) {
+ if( (null!=acodec && acodec.length()>0 && !this.acodec.equals(acodec)) ) {
event_mask |= GLMediaEventListener.EVENT_CHANGE_CODEC;
this.acodec = acodec;
}
@@ -448,101 +1442,132 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
if(0==event_mask) {
return;
}
- attributesUpdated(event_mask);
- }
-
- protected final void attributesUpdated(int event_mask) {
- synchronized(eventListenersLock) {
- for(Iterator<GLMediaEventListener> i = eventListeners.iterator(); i.hasNext(); ) {
- i.next().attributesChanges(this, event_mask, System.currentTimeMillis());
+ if( wasUninitialized ) {
+ if( null != streamWorker ) {
+ throw new InternalError("XXX: StreamWorker not null - "+this);
+ }
+ if( TEXTURE_COUNT_MIN < textureCount || STREAM_ID_NONE == vid ) { // Enable StreamWorker for 'audio only' as well (Bug 918).
+ streamWorker = new StreamWorker();
+ }
+ if( DEBUG ) {
+ System.err.println("XXX Initialize @ updateAttributes: "+this);
}
}
+ attributesUpdated(event_mask);
}
- protected final void newFrameAvailable() {
- frameNumber++;
- synchronized(eventListenersLock) {
- for(Iterator<GLMediaEventListener> i = eventListeners.iterator(); i.hasNext(); ) {
- i.next().newFrameAvailable(this, System.currentTimeMillis());
+
+ protected void setIsGLOriented(final boolean isGLOriented) {
+ if( isInGLOrientation != isGLOriented ) {
+ if( DEBUG ) {
+ System.err.println("XXX gl-orient "+isInGLOrientation+" -> "+isGLOriented);
+ }
+ isInGLOrientation = isGLOriented;
+ if( null != videoFramesOrig ) {
+ for(int i=0; i<videoFramesOrig.length; i++) {
+ videoFramesOrig[i].getTexture().setMustFlipVertically(!isGLOriented);
+ }
+ attributesUpdated(GLMediaEventListener.EVENT_CHANGE_SIZE);
}
}
}
-
+
@Override
- public final synchronized State destroy(GL gl) {
- destroyImpl(gl);
- removeAllImageTextures(gl);
- state = State.Uninitialized;
- return state;
- }
- protected abstract void destroyImpl(GL gl);
+ public final Uri getUri() { return streamLoc; }
@Override
- public final synchronized URLConnection getURLConnection() {
- return urlConn;
- }
+ public final int getVID() { return vid; }
@Override
- public final synchronized String getVideoCodec() {
- return vcodec;
- }
+ public final int getAID() { return aid; }
@Override
- public final synchronized String getAudioCodec() {
- return acodec;
- }
+ public final String getVideoCodec() { return vcodec; }
@Override
- public final synchronized long getTotalFrames() {
- return totalFrames;
- }
+ public final String getAudioCodec() { return acodec; }
@Override
- public final synchronized int getDuration() {
- return duration;
- }
-
+ public final int getVideoFrames() { return videoFrames; }
+
@Override
- public final synchronized long getStreamBitrate() {
- return bps_stream;
- }
+ public final int getAudioFrames() { return audioFrames; }
@Override
- public final synchronized int getVideoBitrate() {
- return bps_video;
- }
-
+ public final int getDuration() { return duration; }
+
@Override
- public final synchronized int getAudioBitrate() {
- return bps_audio;
- }
-
+ public final long getStreamBitrate() { return bps_stream; }
+
@Override
- public final synchronized float getFramerate() {
- return fps;
- }
+ public final int getVideoBitrate() { return bps_video; }
@Override
- public final synchronized int getWidth() {
- return width;
- }
+ public final int getAudioBitrate() { return bps_audio; }
+
+ @Override
+ public final float getFramerate() { return fps; }
+
+ @Override
+ public final boolean isGLOriented() { return isInGLOrientation; }
+
+ @Override
+ public final int getWidth() { return width; }
@Override
- public final synchronized int getHeight() {
- return height;
+ public final int getHeight() { return height; }
+
+ @Override
+ public final String toString() {
+ final float tt = getDuration() / 1000.0f;
+ final String loc = ( null != streamLoc ) ? streamLoc.toString() : "<undefined stream>" ;
+ final int freeVideoFrames = null != videoFramesFree ? videoFramesFree.size() : 0;
+ final int decVideoFrames = null != videoFramesDecoded ? videoFramesDecoded.size() : 0;
+ final int video_scr = video_scr_pts + (int) ( ( Platform.currentTimeMillis() - video_scr_t0 ) * playSpeed );
+ final String camPath = null != cameraPath ? ", camera: "+cameraPath : "";
+ return "GLMediaPlayer["+state+", vSCR "+video_scr+", frames[p "+presentedFrameCount+", d "+decodedFrameCount+", t "+videoFrames+" ("+tt+" s), z "+nullFrameCount+" / "+maxNullFrameCountUntilEOS+"], "+
+ "speed "+playSpeed+", "+bps_stream+" bps, hasSW "+(null!=streamWorker)+
+ ", Texture[count "+textureCount+", free "+freeVideoFrames+", dec "+decVideoFrames+", tagt "+toHexString(textureTarget)+", ifmt "+toHexString(textureInternalFormat)+", fmt "+toHexString(textureFormat)+", type "+toHexString(textureType)+"], "+
+ "Video[id "+vid+", <"+vcodec+">, "+width+"x"+height+", glOrient "+isInGLOrientation+", "+fps+" fps, "+frame_duration+" fdur, "+bps_video+" bps], "+
+ "Audio[id "+aid+", <"+acodec+">, "+bps_audio+" bps, "+audioFrames+" frames], uri "+loc+camPath+"]";
}
@Override
- public final synchronized String toString() {
- final float ct = getCurrentPosition() / 1000.0f, tt = getDuration() / 1000.0f;
- final String loc = ( null != urlConn ) ? urlConn.getURL().toExternalForm() : "<undefined stream>" ;
- return "GLMediaPlayer["+state+", "+frameNumber+"/"+totalFrames+" frames, "+ct+"/"+tt+"s, speed "+playSpeed+", "+bps_stream+" bps, "+
- "Texture[count "+textureCount+", target "+toHexString(textureTarget)+", format "+toHexString(textureFormat)+", type "+toHexString(textureType)+"], "+
- "Stream[Video[<"+vcodec+">, "+width+"x"+height+", "+fps+" fps, "+bps_video+" bsp], "+
- "Audio[<"+acodec+">, "+bps_audio+" bsp]], "+loc+"]";
+ public final String getPerfString() {
+ final long currentTimeMillis = Platform.currentTimeMillis();
+ final int video_scr = video_scr_pts + (int) ( ( currentTimeMillis - video_scr_t0 ) * playSpeed );
+ final int d_vpts = video_pts_last - video_scr;
+ final int audio_scr = (int) ( ( currentTimeMillis - audio_scr_t0 ) * playSpeed );
+ final int audio_pts = getAudioPTSImpl();
+ final int d_apts = audio_pts - audio_scr;
+ return getPerfStringImpl( video_scr, video_pts_last, d_vpts, audio_scr, audio_pts, d_apts, getVideoDPTSAvg() );
+ }
+ private final String getPerfStringImpl(final int video_scr, final int video_pts, final int d_vpts,
+ final int audio_scr, final int audio_pts, final int d_apts,
+ final int video_dpts_avg_diff) {
+ final float tt = getDuration() / 1000.0f;
+ final String audioSinkInfo;
+ final AudioSink audioSink = getAudioSink();
+ if( null != audioSink ) {
+ audioSinkInfo = "AudioSink[frames [p "+audioSink.getEnqueuedFrameCount()+", q "+audioSink.getQueuedFrameCount()+", f "+audioSink.getFreeFrameCount()+", c "+audioSink.getFrameCount()+"], time "+audioSink.getQueuedTime()+", bytes "+audioSink.getQueuedByteCount()+"]";
+ } else {
+ audioSinkInfo = "";
+ }
+ final int freeVideoFrames, decVideoFrames;
+ if( null != videoFramesFree ) {
+ freeVideoFrames = videoFramesFree.size();
+ decVideoFrames = videoFramesDecoded.size();
+ } else {
+ freeVideoFrames = 0;
+ decVideoFrames = 0;
+ }
+ return state+", frames[(p "+presentedFrameCount+", d "+decodedFrameCount+") / "+videoFrames+", "+tt+" s, z "+nullFrameCount+" / "+maxNullFrameCountUntilEOS+"], "+
+ "speed " + playSpeed+", dAV "+( d_vpts - d_apts )+", vSCR "+video_scr+", vpts "+video_pts+", dSCR["+d_vpts+", avrg "+video_dpts_avg_diff+"], "+
+ "aSCR "+audio_scr+", apts "+audio_pts+" ( "+d_apts+" ), "+audioSinkInfo+
+ ", Texture[count "+textureCount+", free "+freeVideoFrames+", dec "+decVideoFrames+"]";
}
@Override
- public final void addEventListener(GLMediaEventListener l) {
+ public final void addEventListener(final GLMediaEventListener l) {
if(l == null) {
return;
}
@@ -552,7 +1577,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
}
@Override
- public final void removeEventListener(GLMediaEventListener l) {
+ public final void removeEventListener(final GLMediaEventListener l) {
if (l == null) {
return;
}
@@ -562,19 +1587,46 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
}
@Override
- public final synchronized GLMediaEventListener[] getEventListeners() {
+ public final GLMediaEventListener[] getEventListeners() {
synchronized(eventListenersLock) {
return eventListeners.toArray(new GLMediaEventListener[eventListeners.size()]);
}
}
- private Object eventListenersLock = new Object();
+ private final Object eventListenersLock = new Object();
+
+ @Override
+ public final Object getAttachedObject(final String name) {
+ return attachedObjects.get(name);
+ }
- protected static final String toHexString(long v) {
+ @Override
+ public final Object attachObject(final String name, final Object obj) {
+ return attachedObjects.put(name, obj);
+ }
+
+ @Override
+ public final Object detachObject(final String name) {
+ return attachedObjects.remove(name);
+ }
+
+ private final HashMap<String, Object> attachedObjects = new HashMap<String, Object>();
+
+ protected static final String toHexString(final long v) {
return "0x"+Long.toHexString(v);
}
- protected static final String toHexString(int v) {
+ protected static final String toHexString(final int v) {
return "0x"+Integer.toHexString(v);
}
-
-} \ No newline at end of file
+ protected static final int getPropIntVal(final Map<String, String> props, final String key) {
+ final String val = props.get(key);
+ try {
+ return Integer.parseInt(val);
+ } catch (final NumberFormatException nfe) {
+ if(DEBUG) {
+ System.err.println("Not a valid integer for <"+key+">: <"+val+">");
+ }
+ }
+ return 0;
+ }
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java b/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java
new file mode 100644
index 000000000..117be5489
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java
@@ -0,0 +1,228 @@
+package jogamp.opengl.util.av;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.SourceDataLine;
+
+import com.jogamp.opengl.util.av.AudioSink;
+
+/***
+ * JavaSound Audio Sink
+ * <p>
+ * FIXME: Parameterize .. all configs .. best via an init-method, passing requested
+ * audio capabilities
+ * </p>
+ */
+public class JavaSoundAudioSink implements AudioSink {
+
+ // Chunk of audio processed at one time
+ public static final int BUFFER_SIZE = 1000;
+ public static final int SAMPLES_PER_BUFFER = BUFFER_SIZE / 2;
+ private static final boolean staticAvailable;
+
+ // Sample time values
+ // public static final double SAMPLE_TIME_IN_SECS = 1.0 / DEFAULT_SAMPLE_RATE;
+ // public static final double BUFFER_TIME_IN_SECS = SAMPLE_TIME_IN_SECS * SAMPLES_PER_BUFFER;
+
+ private javax.sound.sampled.AudioFormat format;
+ private DataLine.Info info;
+ private SourceDataLine auline;
+ private int bufferCount;
+ private final byte [] sampleData = new byte[BUFFER_SIZE];
+ private boolean initialized = false;
+ private AudioSink.AudioFormat chosenFormat = null;
+
+ private volatile boolean playRequested = false;
+ private float volume = 1.0f;
+
+ static {
+ boolean ok = false;
+ try {
+ AudioSystem.getAudioFileTypes();
+ ok = true;
+ } catch (final Throwable t) {
+
+ }
+ staticAvailable=ok;
+ }
+
+ @Override
+ public String toString() {
+ return "JavaSoundSink[init "+initialized+", dataLine "+info+", source "+auline+", bufferCount "+bufferCount+
+ ", chosen "+chosenFormat+", jsFormat "+format;
+ }
+
+ @Override
+ public final float getPlaySpeed() { return 1.0f; } // FIXME
+
+ @Override
+ public final boolean setPlaySpeed(final float rate) {
+ return false; // FIXME
+ }
+
+ @Override
+ public final float getVolume() {
+ // FIXME
+ return volume;
+ }
+
+ @Override
+ public final boolean setVolume(final float v) {
+ // FIXME
+ volume = v;
+ return true;
+ }
+
+ @Override
+ public AudioSink.AudioFormat getPreferredFormat() {
+ return DefaultFormat;
+ }
+
+ @Override
+ public final int getMaxSupportedChannels() {
+ return 2;
+ }
+
+ @Override
+ public final boolean isSupported(final AudioSink.AudioFormat format) {
+ return true;
+ }
+
+ @Override
+ public boolean init(final AudioSink.AudioFormat requestedFormat, final float frameDuration, final int initialQueueSize, final int queueGrowAmount, final int queueLimit) {
+ if( !staticAvailable ) {
+ return false;
+ }
+ // Create the audio format we wish to use
+ format = new javax.sound.sampled.AudioFormat(requestedFormat.sampleRate, requestedFormat.sampleSize, requestedFormat.channelCount, requestedFormat.signed, !requestedFormat.littleEndian);
+
+ // Create dataline info object describing line format
+ info = new DataLine.Info(SourceDataLine.class, format);
+
+ // Clear buffer initially
+ Arrays.fill(sampleData, (byte) 0);
+ try{
+ // Get line to write data to
+ auline = (SourceDataLine) AudioSystem.getLine(info);
+ auline.open(format);
+ auline.start();
+ System.out.println("JavaSound audio sink");
+ initialized=true;
+ chosenFormat = requestedFormat;
+ } catch (final Exception e) {
+ initialized=false;
+ }
+ return true;
+ }
+
+ @Override
+ public final AudioFormat getChosenFormat() {
+ return chosenFormat;
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return playRequested && auline.isRunning();
+ }
+
+ @Override
+ public void play() {
+ if( null != auline ) {
+ playRequested = true;
+ playImpl();
+ }
+ }
+ private void playImpl() {
+ if( playRequested && !auline.isRunning() ) {
+ auline.start();
+ }
+ }
+
+ @Override
+ public void pause() {
+ if( null != auline ) {
+ playRequested = false;
+ auline.stop();
+ }
+ }
+
+ @Override
+ public void flush() {
+ if( null != auline ) {
+ playRequested = false;
+ auline.stop();
+ auline.flush();
+ }
+ }
+
+ @Override
+ public final int getEnqueuedFrameCount() {
+ return 0; // FIXME
+ }
+
+ @Override
+ public int getFrameCount() {
+ return 1;
+ }
+
+ @Override
+ public int getQueuedFrameCount() {
+ return 0;
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ @Override
+ public void destroy() {
+ initialized = false;
+ chosenFormat = null;
+ // FIXEM: complete code!
+ }
+
+ @Override
+ public AudioFrame enqueueData(final int pts, final ByteBuffer byteBuffer, final int byteCount) {
+ final byte[] bytes = new byte[byteCount];
+ final int p = byteBuffer.position();
+ byteBuffer.get(bytes, 0, byteCount);
+ byteBuffer.position(p);
+
+ int written = 0;
+ int len;
+ int bytesLeft = byteCount;
+ while (bytesLeft > 0) {
+ len = auline.write(bytes, written, byteCount);
+ bytesLeft -= len;
+ written += len;
+ }
+ playImpl();
+ return new AudioDataFrame(pts, chosenFormat.getBytesDuration(byteCount), byteBuffer, byteCount);
+ }
+
+ @Override
+ public int getQueuedByteCount() {
+ return auline.getBufferSize() - auline.available();
+ }
+
+ @Override
+ public int getFreeFrameCount() {
+ return auline.available();
+ }
+
+ @Override
+ public int getQueuedTime() {
+ return getQueuedTimeImpl( getQueuedByteCount() );
+ }
+ private final int getQueuedTimeImpl(final int byteCount) {
+ final int bytesPerSample = chosenFormat.sampleSize >>> 3; // /8
+ return byteCount / ( chosenFormat.channelCount * bytesPerSample * ( chosenFormat.sampleRate / 1000 ) );
+ }
+
+ @Override
+ public final int getPTS() { return 0; } // FIXME
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java b/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java
new file mode 100644
index 000000000..7e27f17c3
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java
@@ -0,0 +1,181 @@
+/**
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.av;
+
+import java.nio.ByteBuffer;
+
+import com.jogamp.opengl.util.av.AudioSink;
+
+public class NullAudioSink implements AudioSink {
+
+ private volatile float playSpeed = 1.0f;
+ private volatile boolean playRequested = false;
+ private volatile int playingPTS = AudioFrame.INVALID_PTS;
+ private float volume = 1.0f;
+
+ private AudioFormat chosenFormat;
+ private boolean initialized;
+
+ public NullAudioSink() {
+ initialized = true;
+ chosenFormat = null;
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ @Override
+ public final float getPlaySpeed() { return playSpeed; }
+
+ @Override
+ public final boolean setPlaySpeed(float rate) {
+ if( Math.abs(1.0f - rate) < 0.01f ) {
+ rate = 1.0f;
+ }
+ playSpeed = rate;
+ return true;
+ }
+
+ @Override
+ public final float getVolume() {
+ // FIXME
+ return volume;
+ }
+
+ @Override
+ public final boolean setVolume(final float v) {
+ // FIXME
+ volume = v;
+ return true;
+ }
+
+ @Override
+ public AudioFormat getPreferredFormat() {
+ return DefaultFormat;
+ }
+
+ @Override
+ public final int getMaxSupportedChannels() {
+ return 8;
+ }
+
+ @Override
+ public final boolean isSupported(final AudioFormat format) {
+ /**
+ * If we like to emulate constraints ..
+ *
+ if( format.planar || !format.littleEndian ) {
+ return false;
+ }
+ if( format.sampleRate != DefaultFormat.sampleRate ) {
+ return false;
+ }
+ */
+ return true;
+ }
+
+ @Override
+ public boolean init(final AudioFormat requestedFormat, final float frameDuration, final int initialQueueSize, final int queueGrowAmount, final int queueLimit) {
+ chosenFormat = requestedFormat;
+ return true;
+ }
+
+ @Override
+ public final AudioFormat getChosenFormat() {
+ return chosenFormat;
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return playRequested;
+ }
+
+ @Override
+ public void play() {
+ playRequested = true;
+ }
+
+ @Override
+ public void pause() {
+ playRequested = false;
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void destroy() {
+ initialized = false;
+ chosenFormat = null;
+ }
+
+ @Override
+ public final int getEnqueuedFrameCount() {
+ return 0;
+ }
+
+ @Override
+ public int getFrameCount() {
+ return 0;
+ }
+
+ @Override
+ public int getQueuedFrameCount() {
+ return 0;
+ }
+
+ @Override
+ public int getQueuedByteCount() {
+ return 0;
+ }
+
+ @Override
+ public int getQueuedTime() {
+ return 0;
+ }
+
+ @Override
+ public final int getPTS() { return playingPTS; }
+
+ @Override
+ public int getFreeFrameCount() {
+ return 1;
+ }
+
+ @Override
+ public AudioFrame enqueueData(final int pts, final ByteBuffer bytes, final int byteCount) {
+ if( !initialized || null == chosenFormat ) {
+ return null;
+ }
+ playingPTS = pts;
+ return null;
+ }
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
index cd48c3962..4a15c7422 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
@@ -3,14 +3,14 @@
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
- *
+ *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
@@ -20,7 +20,7 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
@@ -32,12 +32,15 @@ import java.net.URLConnection;
import java.nio.ByteBuffer;
import javax.media.opengl.GL;
+import javax.media.opengl.GLException;
import javax.media.opengl.GLProfile;
import jogamp.opengl.util.av.GLMediaPlayerImpl;
import com.jogamp.common.nio.Buffers;
+import com.jogamp.common.os.Platform;
import com.jogamp.common.util.IOUtil;
+import com.jogamp.opengl.util.av.GLMediaPlayer;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureData;
import com.jogamp.opengl.util.texture.TextureIO;
@@ -49,114 +52,126 @@ import com.jogamp.opengl.util.texture.TextureSequence;
*/
public class NullGLMediaPlayer extends GLMediaPlayerImpl {
private TextureData texData = null;
- private TextureSequence.TextureFrame frame = null;
private int pos_ms = 0;
- private int pos_start = 0;
-
+ private long pos_start = 0;
+
public NullGLMediaPlayer() {
super();
- this.setTextureCount(1);
+
}
@Override
- protected boolean setPlaySpeedImpl(float rate) {
+ protected final boolean setPlaySpeedImpl(final float rate) {
return false;
}
@Override
- protected boolean startImpl() {
- pos_start = (int)System.currentTimeMillis();
+ protected final boolean playImpl() {
+ pos_start = Platform.currentTimeMillis();
return true;
}
@Override
- protected boolean pauseImpl() {
+ protected final boolean pauseImpl() {
return true;
}
@Override
- protected boolean stopImpl() {
- return true;
- }
-
- @Override
- protected int seekImpl(int msec) {
+ protected final int seekImpl(final int msec) {
pos_ms = msec;
validatePos();
return pos_ms;
}
-
- @Override
- protected TextureSequence.TextureFrame getLastTextureImpl() {
- return frame;
- }
@Override
- protected TextureSequence.TextureFrame getNextTextureImpl(GL gl, boolean blocking) {
- return frame;
+ protected final int getNextTextureImpl(final GL gl, final TextureFrame nextFrame) {
+ final int pts = getAudioPTSImpl();
+ nextFrame.setPTS( pts );
+ return pts;
}
-
+
@Override
- protected int getCurrentPositionImpl() {
- pos_ms = (int)System.currentTimeMillis() - pos_start;
+ protected final int getAudioPTSImpl() {
+ pos_ms = (int) ( Platform.currentTimeMillis() - pos_start );
validatePos();
return pos_ms;
}
@Override
- protected void destroyImpl(GL gl) {
+ protected final void destroyImpl(final GL gl) {
+ if(null != texData) {
+ texData.destroy();
+ texData = null;
+ }
}
-
- @Override
- protected void initGLStreamImpl(GL gl, int[] texNames) throws IOException {
+
+ public final static TextureData createTestTextureData() {
+ TextureData res = null;
try {
- URLConnection urlConn = IOUtil.getResource("jogl/util/data/av/test-ntsc01-160x90.png", this.getClass().getClassLoader());
+ final URLConnection urlConn = IOUtil.getResource("jogl/util/data/av/test-ntsc01-28x16.png", NullGLMediaPlayer.class.getClassLoader());
if(null != urlConn) {
- texData = TextureIO.newTextureData(GLProfile.getGL2ES2(), urlConn.getInputStream(), false, TextureIO.PNG);
+ res = TextureIO.newTextureData(GLProfile.getGL2ES2(), urlConn.getInputStream(), false, TextureIO.PNG);
}
- } catch (Exception e) {
+ } catch (final Exception e) {
e.printStackTrace();
}
- if(null != texData) {
- width = texData.getWidth();
- height = texData.getHeight();
- } else {
- width = 640;
- height = 480;
- ByteBuffer buffer = Buffers.newDirectByteBuffer(width*height*4);
+ if(null == res) {
+ final int w = 160;
+ final int h = 90;
+ final ByteBuffer buffer = Buffers.newDirectByteBuffer(w*h*4);
while(buffer.hasRemaining()) {
buffer.put((byte) 0xEA); buffer.put((byte) 0xEA); buffer.put((byte) 0xEA); buffer.put((byte) 0xEA);
}
buffer.rewind();
- texData = new TextureData(GLProfile.getGL2ES2(),
- GL.GL_RGBA, width, height, 0,
- GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false,
+ res = new TextureData(GLProfile.getGL2ES2(),
+ GL.GL_RGBA, w, h, 0,
+ GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false,
false, false, buffer, null);
}
- fps = 24f;
- duration = 10*60*1000; // msec
- totalFrames = (int) ( (duration/1000)*fps );
- vcodec = "png-static";
+ return res;
+ }
+
+ @Override
+ protected final void initStreamImpl(final int vid, final int aid) throws IOException {
+ texData = createTestTextureData();
+ final float _fps = 24f;
+ final int _duration = 10*60*1000; // msec
+ final int _totalFrames = (int) ( (_duration/1000)*_fps );
+ updateAttributes(0 /* fake */, GLMediaPlayer.STREAM_ID_NONE,
+ texData.getWidth(), texData.getHeight(), 0,
+ 0, 0, _fps,
+ _totalFrames, 0, _duration, "png-static", null);
+ }
+ @Override
+ protected final void initGLImpl(final GL gl) throws IOException, GLException {
+ setIsGLOriented(true);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Returns {@link GLMediaPlayer#TEXTURE_COUNT_MIN}.
+ * </p>
+ */
+ @Override
+ protected int validateTextureCount(final int desiredTextureCount) {
+ return TEXTURE_COUNT_MIN;
}
-
+
@Override
- protected TextureSequence.TextureFrame createTexImage(GL gl, int idx, int[] tex) {
- Texture texture = super.createTexImageImpl(gl, idx, tex, width, height, false);
+ protected final TextureSequence.TextureFrame createTexImage(final GL gl, final int texName) {
+ final Texture texture = super.createTexImageImpl(gl, texName, getWidth(), getHeight());
if(null != texData) {
texture.updateImage(gl, texData);
- texData.destroy();
- texData = null;
- }
- frame = new TextureSequence.TextureFrame( texture );
- return frame;
+ }
+ return new TextureSequence.TextureFrame( texture );
}
-
+
@Override
- protected void destroyTexImage(GL gl, TextureSequence.TextureFrame imgTex) {
- frame = null;
- super.destroyTexImage(gl, imgTex);
+ protected final void destroyTexFrame(final GL gl, final TextureSequence.TextureFrame frame) {
+ super.destroyTexFrame(gl, frame);
}
-
+
private void validatePos() {
boolean considerPausing = false;
if( 0 > pos_ms) {
@@ -166,8 +181,8 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl {
pos_ms = getDuration();
considerPausing = true;
}
- if(considerPausing && state == State.Playing) {
- state = State.Paused;
+ if( considerPausing && State.Playing == getState() ) {
+ setState(State.Paused);
}
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/VideoPixelFormat.java b/src/jogl/classes/jogamp/opengl/util/av/VideoPixelFormat.java
new file mode 100644
index 000000000..44d83e78d
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/VideoPixelFormat.java
@@ -0,0 +1,191 @@
+/**
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.av;
+
+/** FFMPEG/libAV compatible video pixel format */
+public enum VideoPixelFormat {
+ // NONE= -1,
+ /** planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples) */
+ YUV420P,
+ /** packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr ( sharing Cb and Cr w/ 2 pixels )*/
+ YUYV422,
+ /** packed RGB 8:8:8, 24bpp, RGBRGB... */
+ RGB24,
+ /** packed RGB 8:8:8, 24bpp, BGRBGR... */
+ BGR24,
+ /** planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples) */
+ YUV422P,
+ /** planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples) */
+ YUV444P,
+ /** planar YUV 4:1:0, 9bpp, (1 Cr & Cb sample per 4x4 Y samples) */
+ YUV410P,
+ /** planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples) */
+ YUV411P,
+ /** Y, 8bpp */
+ GRAY8,
+ /** Y, 1bpp, 0 is white, 1 is black, in each byte pixels are ordered from the msb to the lsb */
+ MONOWHITE,
+ /** Y, 1bpp, 0 is black, 1 is white, in each byte pixels are ordered from the msb to the lsb */
+ MONOBLACK,
+ /** 8 bit with RGB32 palette */
+ PAL8,
+ /** planar YUV 4:2:0, 12bpp, full scale (JPEG), deprecated in favor of YUV420P and setting color_range */
+ YUVJ420P,
+ /** planar YUV 4:2:2, 16bpp, full scale (JPEG), deprecated in favor of YUV422P and setting color_range */
+ YUVJ422P,
+ /** planar YUV 4:4:4, 24bpp, full scale (JPEG), deprecated in favor of YUV444P and setting color_range */
+ YUVJ444P,
+ /** XVideo Motion Acceleration via common packet passing */
+ XVMC_MPEG2_MC,
+ /** */
+ XVMC_MPEG2_IDCT,
+ /** packed YUV 4:2:2, 16bpp, Cb Y0 Cr Y1 */
+ UYVY422,
+ /** packed YUV 4:1:1, 12bpp, Cb Y0 Y1 Cr Y2 Y3 */
+ UYYVYY411,
+ /** packed RGB 3:3:2, 8bpp, (msb)2B 3G 3R(lsb) */
+ BGR8,
+ /** packed RGB 1:2:1 bitstream, 4bpp, (msb)1B 2G 1R(lsb), a byte contains two pixels, the first pixel in the byte is the one composed by the 4 msb bits */
+ BGR4,
+ /** packed RGB 1:2:1, 8bpp, (msb)1B 2G 1R(lsb) */
+ BGR4_BYTE,
+ /** packed RGB 3:3:2, 8bpp, (msb)2R 3G 3B(lsb) */
+ RGB8,
+ /** packed RGB 1:2:1 bitstream, 4bpp, (msb)1R 2G 1B(lsb), a byte contains two pixels, the first pixel in the byte is the one composed by the 4 msb bits */
+ RGB4,
+ /** packed RGB 1:2:1, 8bpp, (msb)1R 2G 1B(lsb) */
+ RGB4_BYTE,
+ /** planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V) */
+ NV12,
+ /** as above, but U and V bytes are swapped */
+ NV21,
+
+ /** packed ARGB 8:8:8:8, 32bpp, ARGBARGB... */
+ ARGB,
+ /** packed RGBA 8:8:8:8, 32bpp, RGBARGBA... */
+ RGBA,
+ /** packed ABGR 8:8:8:8, 32bpp, ABGRABGR... */
+ ABGR,
+ /** packed BGRA 8:8:8:8, 32bpp, BGRABGRA... */
+ BGRA,
+
+ /** Y, 16bpp, big-endian */
+ GRAY16BE,
+ /** Y , 16bpp, little-endian */
+ GRAY16LE,
+ /** planar YUV 4:4:0 (1 Cr & Cb sample per 1x2 Y samples) */
+ YUV440P,
+ /** planar YUV 4:4:0 full scale (JPEG), deprecated in favor of YUV440P and setting color_range */
+ YUVJ440P,
+ /** planar YUV 4:2:0, 20bpp, (1 Cr & Cb sample per 2x2 Y & A samples) */
+ YUVA420P,
+ /** H.264 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers */
+ VDPAU_H264,
+ /** MPEG-1 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers */
+ VDPAU_MPEG1,
+ /** MPEG-2 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers */
+ VDPAU_MPEG2,
+ /** WMV3 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers */
+ VDPAU_WMV3,
+ /** VC-1 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers */
+ VDPAU_VC1,
+ /** packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the 2-byte value for each R/G/B component is stored as big-endian */
+ RGB48BE,
+ /** packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the 2-byte value for each R/G/B component is stored as little-endian */
+ RGB48LE,
+
+ RGB565BE, ///< packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), big-endian
+ RGB565LE, ///< packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), little-endian
+ RGB555BE, ///< packed RGB 5:5:5, 16bpp, (msb)1A 5R 5G 5B(lsb), big-endian, most significant bit to 0
+ RGB555LE, ///< packed RGB 5:5:5, 16bpp, (msb)1A 5R 5G 5B(lsb), little-endian, most significant bit to 0
+
+ BGR565BE, ///< packed BGR 5:6:5, 16bpp, (msb) 5B 6G 5R(lsb), big-endian
+ BGR565LE, ///< packed BGR 5:6:5, 16bpp, (msb) 5B 6G 5R(lsb), little-endian
+ BGR555BE, ///< packed BGR 5:5:5, 16bpp, (msb)1A 5B 5G 5R(lsb), big-endian, most significant bit to 1
+ BGR555LE, ///< packed BGR 5:5:5, 16bpp, (msb)1A 5B 5G 5R(lsb), little-endian, most significant bit to 1
+
+ VAAPI_MOCO, ///< HW acceleration through VA API at motion compensation entry-point, Picture.data[3] contains a vaapi_render_state struct which contains macroblocks as well as various fields extracted from headers
+ VAAPI_IDCT, ///< HW acceleration through VA API at IDCT entry-point, Picture.data[3] contains a vaapi_render_state struct which contains fields extracted from headers
+ VAAPI_VLD, ///< HW decoding through VA API, Picture.data[3] contains a vaapi_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers
+
+ YUV420P16LE, ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
+ YUV420P16BE, ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
+ YUV422P16LE, ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
+ YUV422P16BE, ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
+ YUV444P16LE, ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
+ YUV444P16BE, ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
+ VDPAU_MPEG4, ///< MPEG4 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers
+ DXVA2_VLD, ///< HW decoding through DXVA2, Picture.data[3] contains a LPDIRECT3DSURFACE9 pointer
+
+ RGB444LE, ///< packed RGB 4:4:4, 16bpp, (msb)4A 4R 4G 4B(lsb), little-endian, most significant bits to 0
+ RGB444BE, ///< packed RGB 4:4:4, 16bpp, (msb)4A 4R 4G 4B(lsb), big-endian, most significant bits to 0
+ BGR444LE, ///< packed BGR 4:4:4, 16bpp, (msb)4A 4B 4G 4R(lsb), little-endian, most significant bits to 1
+ BGR444BE, ///< packed BGR 4:4:4, 16bpp, (msb)4A 4B 4G 4R(lsb), big-endian, most significant bits to 1
+ Y400A, ///< 8bit gray, 8bit alpha
+ BGR48BE, ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the 2-byte value for each R/G/B component is stored as big-endian
+ BGR48LE, ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the 2-byte value for each R/G/B component is stored as little-endian
+ YUV420P9BE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
+ YUV420P9LE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
+ YUV420P10BE,///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
+ YUV420P10LE,///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
+ YUV422P10BE,///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
+ YUV422P10LE,///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
+ YUV444P9BE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
+ YUV444P9LE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
+ YUV444P10BE,///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
+ YUV444P10LE,///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
+ YUV422P9BE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
+ YUV422P9LE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
+ VDA_VLD, ///< hardware decoding through VDA
+ GBRP, ///< planar GBR 4:4:4 24bpp
+ GBRP9BE, ///< planar GBR 4:4:4 27bpp, big endian
+ GBRP9LE, ///< planar GBR 4:4:4 27bpp, little endian
+ GBRP10BE, ///< planar GBR 4:4:4 30bpp, big endian
+ GBRP10LE, ///< planar GBR 4:4:4 30bpp, little endian
+ GBRP16BE, ///< planar GBR 4:4:4 48bpp, big endian
+ GBRP16LE, ///< planar GBR 4:4:4 48bpp, little endian
+ COUNT ///< number of pixel formats in this list
+ ;
+ /**
+ * Returns the matching PixelFormat value corresponding to the given PixelFormat's integer ordinal.
+ * <pre>
+ * given:
+ * ordinal = enumValue.ordinal()
+ * reverse:
+ * enumValue = EnumClass.values()[ordinal]
+ * </pre>
+ * @throws IllegalArgumentException if the given ordinal is out of range, i.e. not within [ 0 .. PixelFormat.values().length-1 ]
+ */
+ public static VideoPixelFormat valueOf(final int ordinal) throws IllegalArgumentException {
+ final VideoPixelFormat[] all = VideoPixelFormat.values();
+ if( 0 <= ordinal && ordinal < all.length ) {
+ return all[ordinal];
+ }
+ throw new IllegalArgumentException("Ordinal "+ordinal+" out of range of PixelFormat.values()[0.."+(all.length-1)+"]");
+ }
+} \ No newline at end of file
diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java
index 32c863553..42a908f93 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java
@@ -3,14 +3,14 @@
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
- *
+ *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
@@ -20,20 +20,20 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
-
+
package jogamp.opengl.util.av.impl;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import javax.media.opengl.GLProfile;
@@ -41,225 +41,380 @@ import javax.media.opengl.GLProfile;
import com.jogamp.common.os.DynamicLibraryBundle;
import com.jogamp.common.os.DynamicLibraryBundleInfo;
import com.jogamp.common.util.RunnableExecutor;
+import com.jogamp.common.util.VersionNumber;
/**
- * FIXME: We need native structure access methods to deal with API changes
- * in the libav headers, which break binary compatibility!
- * Currently we are binary compatible w/ [0.6 ?, ] 0.7 and 0.8 but not w/ trunk.
- *
- * ChangeList for trunk:
- * Thu Jan 12 11:21:02 2012 a17479dfce67fbea2d0a1bf303010dce1e79059f major 53 -> 54
- * Mon Feb 27 22:40:11 2012 ee42df8a35c2b795f524c856834d0823dbd4e75d reorder AVStream and AVFormatContext
- * Tue Feb 28 12:07:53 2012 322537478b63c6bc01e640643550ff539864d790 minor 1 -> 2
+ * See {@link FFMPEGMediaPlayer#compatibility}.
*/
class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo {
- private static List<String> glueLibNames = new ArrayList<String>(); // none
-
- private static final int symbolCount = 31;
- private static String[] symbolNames = {
- "avcodec_version",
+ private static final boolean DEBUG = FFMPEGMediaPlayer.DEBUG || DynamicLibraryBundleInfo.DEBUG;
+
+ private static final List<String> glueLibNames = new ArrayList<String>(); // none
+
+ private static final int symbolCount = 65;
+ private static final String[] symbolNames = {
+ "avutil_version",
"avformat_version",
-/* 3 */ "avutil_version",
-
+ "avcodec_version",
+ "avresample_version",
+/* 5 */ "swresample_version",
+
// libavcodec
- "avcodec_close",
- "avcodec_string",
- "avcodec_find_decoder",
- "avcodec_open2", // 53.6.0 (opt)
- "avcodec_open",
- "avcodec_alloc_frame",
- "avcodec_default_get_buffer",
- "avcodec_default_release_buffer",
- "av_free_packet",
+ "avcodec_register_all",
+ "avcodec_close",
+ "avcodec_string",
+ "avcodec_find_decoder",
+ "avcodec_open2", // 53.6.0 (opt)
+ "avcodec_alloc_frame",
+ "avcodec_get_frame_defaults",
+ "avcodec_free_frame", // 54.28.0 (opt)
+ "avcodec_default_get_buffer", // <= 54 (opt), else sp_avcodec_default_get_buffer2
+ "avcodec_default_release_buffer", // <= 54 (opt), else sp_av_frame_unref
+ "avcodec_default_get_buffer2", // 55 (opt)
+ "avcodec_get_edge_width",
+ "av_image_fill_linesizes",
+ "avcodec_align_dimensions",
+ "avcodec_align_dimensions2",
+ "avcodec_flush_buffers",
+ "av_init_packet",
+ "av_new_packet",
+ "av_destruct_packet",
+ "av_free_packet",
"avcodec_decode_audio4", // 53.25.0 (opt)
- "avcodec_decode_audio3", // 52.23.0
-/* 15 */ "avcodec_decode_video2", // 52.23.0
-
+/* 27 */ "avcodec_decode_video2", // 52.23.0
+
// libavutil
- "av_pix_fmt_descriptors",
- "av_free",
-/* 18 */ "av_get_bits_per_pixel",
-
+ "av_pix_fmt_descriptors",
+ "av_frame_unref", // 55.0.0 (opt)
+ "av_realloc",
+ "av_free",
+ "av_get_bits_per_pixel",
+ "av_samples_get_buffer_size",
+ "av_get_bytes_per_sample", // 51.4.0
+ "av_opt_set_int", // 51.12.0
+ "av_dict_get",
+ "av_dict_count", // 54.* (opt)
+ "av_dict_set",
+/* 28 */ "av_dict_free",
+
// libavformat
"avformat_alloc_context",
"avformat_free_context", // 52.96.0 (opt)
"avformat_close_input", // 53.17.0 (opt)
- "av_close_input_file",
- "av_register_all",
- "avformat_open_input",
- "av_dump_format",
+ "av_register_all",
+ "av_find_input_format",
+ "avformat_open_input",
+ "av_dump_format",
"av_read_frame",
"av_seek_frame",
+ "avformat_seek_file", // ??? (opt)
+ "av_read_play",
+ "av_read_pause",
"avformat_network_init", // 53.13.0 (opt)
"avformat_network_deinit", // 53.13.0 (opt)
- "avformat_find_stream_info", // 53.3.0 (opt)
-/* 29 */ "av_find_stream_info",
- };
-
- // alternate symbol names
- private static String[][] altSymbolNames = {
- { "avcodec_open", "avcodec_open2" }, // old, 53.6.0
- { "avcodec_decode_audio3", "avcodec_decode_audio4" }, // old, 53.25.0
- { "av_close_input_file", "avformat_close_input" }, // old, 53.17.0
- { "av_find_stream_info", "avformat_find_stream_info" }, // old, 53.3.0
+/* 54 */ "avformat_find_stream_info", // 53.3.0 (opt)
+
+ // libavdevice
+/* 55 */ "avdevice_register_all", // ???
+
+ // libavresample
+ "avresample_alloc_context", // 1.0.1
+ "avresample_open",
+ "avresample_close",
+ "avresample_free",
+/* 60 */ "avresample_convert",
+
+ // libavresample
+ "av_opt_set_sample_fmt", // actually lavu .. but exist only w/ swresample!
+ "swr_alloc",
+ "swr_init",
+ "swr_free",
+/* 65 */ "swr_convert",
+
};
-
+
// optional symbol names
- private static String[] optionalSymbolNames = {
- "avformat_free_context", // 52.96.0 (opt)
- "avformat_network_init", // 53.13.0 (opt)
- "avformat_network_deinit", // 53.13.0 (opt)
+ private static final String[] optionalSymbolNames = {
+ "avformat_seek_file", // ??? (opt)
+ "avcodec_free_frame", // 54.28.0 (opt)
+ "av_frame_unref", // 55.0.0 (opt)
+ "av_dict_count", // 54.* (opt)
+ "avcodec_default_get_buffer", // <= 54 (opt), else sp_avcodec_default_get_buffer2
+ "avcodec_default_release_buffer", // <= 54 (opt), else sp_av_frame_unref
+ "avcodec_default_get_buffer2", // 55 (opt)
+
+ // libavdevice
+ "avdevice_register_all", // 53.0.0 (opt)
+
+ // libavresample
+ "avresample_version", // 1.0.1
+ "avresample_alloc_context", // 1.0.1
+ "avresample_open",
+ "avresample_close",
+ "avresample_free",
+ "avresample_convert",
+
+ // libavresample
+ "av_opt_set_sample_fmt", // actually lavu .. but exist only w/ swresample!
+ "swresample_version", // 0
+ "swr_alloc",
+ "swr_init",
+ "swr_free",
+ "swr_convert",
};
-
- private static long[] symbolAddr;
+
+ private static final long[] symbolAddr = new long[symbolCount];
private static final boolean ready;
-
+ private static final boolean libsUFCLoaded;
+ private static final boolean avresampleLoaded; // optional
+ private static final boolean swresampleLoaded; // optional
+ private static final boolean avdeviceLoaded; // optional
+ static final VersionNumber avCodecVersion;
+ static final VersionNumber avFormatVersion;
+ static final VersionNumber avUtilVersion;
+ static final VersionNumber avResampleVersion;
+ static final VersionNumber swResampleVersion;
+ private static final FFMPEGNatives natives;
+
+ private static final int LIB_IDX_UTI = 0;
+ private static final int LIB_IDX_FMT = 1;
+ private static final int LIB_IDX_COD = 2;
+ private static final int LIB_IDX_DEV = 3;
+ private static final int LIB_IDX_AVR = 4;
+ private static final int LIB_IDX_SWR = 5;
+
static {
- // native ffmpeg media player implementation is included in jogl_desktop and jogl_mobile
+ // native ffmpeg media player implementation is included in jogl_desktop and jogl_mobile
GLProfile.initSingleton();
boolean _ready = false;
+ /** util, format, codec, device, avresample, swresample */
+ final boolean[] _loaded= new boolean[6];
+ /** util, format, codec, avresample, swresample */
+ final VersionNumber[] _versions = new VersionNumber[5];
try {
- _ready = initSymbols();
- } catch (Throwable t) {
+ _ready = initSymbols(_loaded, _versions);
+ } catch (final Throwable t) {
t.printStackTrace();
}
- ready = _ready;
- if(!ready) {
- System.err.println("FFMPEG: Not Available");
+ libsUFCLoaded = _loaded[LIB_IDX_UTI] && _loaded[LIB_IDX_FMT] && _loaded[LIB_IDX_COD];
+ avdeviceLoaded = _loaded[LIB_IDX_DEV];
+ avresampleLoaded = _loaded[LIB_IDX_AVR];
+ swresampleLoaded = _loaded[LIB_IDX_SWR];
+ avUtilVersion = _versions[0];
+ avFormatVersion = _versions[1];
+ avCodecVersion = _versions[2];
+ avResampleVersion = _versions[3];
+ swResampleVersion = _versions[4];
+ if(!libsUFCLoaded) {
+ System.err.println("LIB_AV Not Available: lavu, lavc, lavu");
+ natives = null;
+ ready = false;
+ } else if(!_ready) {
+ System.err.println("LIB_AV Not Matching");
+ natives = null;
+ ready = false;
+ } else {
+ final int avCodecMajor = avCodecVersion.getMajor();
+ final int avFormatMajor = avFormatVersion.getMajor();
+ final int avUtilMajor = avUtilVersion.getMajor();
+ if( avCodecMajor == 53 && avFormatMajor == 53 && avUtilMajor == 51 ) {
+ // lavc53.lavf53.lavu51
+ natives = new FFMPEGv08Natives();
+ } else if( avCodecMajor == 54 && avFormatMajor == 54 && avUtilMajor == 52 ) {
+ // lavc54.lavf54.lavu52.lavr01
+ natives = new FFMPEGv09Natives();
+ } else if( avCodecMajor == 55 && avFormatMajor == 55 && ( avUtilMajor == 52 || avUtilMajor == 53 ) ) {
+ // lavc55.lavf55.lavu52.lavr01 (ffmpeg) or lavc55.lavf55.lavu53.lavr01 (libav)
+ natives = new FFMPEGv10Natives();
+ } else {
+ System.err.println("LIB_AV No Version/Native-Impl Match");
+ natives = null;
+ }
+ if( null != natives && FFMPEGStaticNatives.initIDs0() ) {
+ ready = natives.initSymbols0(symbolAddr, symbolCount);
+ } else {
+ ready = false;
+ }
}
}
-
+
+ static boolean libsLoaded() { return libsUFCLoaded; }
+ static boolean avDeviceLoaded() { return avdeviceLoaded; }
+ static boolean avResampleLoaded() { return avresampleLoaded; }
+ static boolean swResampleLoaded() { return swresampleLoaded; }
+ static FFMPEGNatives getNatives() { return natives; }
static boolean initSingleton() { return ready; }
-
- private static boolean initSymbols() {
- final DynamicLibraryBundle dl = new DynamicLibraryBundle(new FFMPEGDynamicLibraryBundleInfo());
- final boolean avutilLoaded = dl.isToolLibLoaded(0);
- final boolean avformatLoaded = dl.isToolLibLoaded(1);
- final boolean avcodecLoaded = dl.isToolLibLoaded(2);
- if(!avutilLoaded || !avformatLoaded || !avcodecLoaded) {
- throw new RuntimeException("FFMPEG Tool library incomplete: [ avutil "+avutilLoaded+", avformat "+avformatLoaded+", avcodec "+avcodecLoaded+"]");
- }
- if(!dl.isToolLibComplete()) {
- throw new RuntimeException("FFMPEG Tool libraries incomplete");
+
+ /**
+ * @param loaded 6: util, format, codec, device, avresample, swresample
+ * @param versions 5: util, format, codec, avresample, swresample
+ * @return
+ */
+ private static final boolean initSymbols(final boolean[] loaded, final VersionNumber[] versions) {
+ for(int i=0; i<6; i++) {
+ loaded[i] = false;
+ }
+ final DynamicLibraryBundle dl = AccessController.doPrivileged(new PrivilegedAction<DynamicLibraryBundle>() {
+ @Override
+ public DynamicLibraryBundle run() {
+ return new DynamicLibraryBundle(new FFMPEGDynamicLibraryBundleInfo());
+ } } );
+ dl.toString();
+ for(int i=0; i<6; i++) {
+ loaded[i] = dl.isToolLibLoaded(i);
+ }
+ if( !loaded[LIB_IDX_UTI] || !loaded[LIB_IDX_FMT] || !loaded[LIB_IDX_COD] ) {
+ throw new RuntimeException("FFMPEG Tool library incomplete: [ avutil "+loaded[LIB_IDX_UTI]+", avformat "+loaded[LIB_IDX_FMT]+", avcodec "+loaded[LIB_IDX_COD]+"]");
}
if(symbolNames.length != symbolCount) {
throw new InternalError("XXX0 "+symbolNames.length+" != "+symbolCount);
}
- symbolAddr = new long[symbolCount];
-
+
// optional symbol name set
final Set<String> optionalSymbolNameSet = new HashSet<String>();
optionalSymbolNameSet.addAll(Arrays.asList(optionalSymbolNames));
-
- // alternate symbol name mapping to indexed array
- final Map<String, Integer> mAltSymbolNames = new HashMap<String, Integer>();
- final int[][] iAltSymbolNames = new int[altSymbolNames.length][];
- {
- final List<String> symbolNameList = Arrays.asList(symbolNames);
- for(int i=0; i<altSymbolNames.length; i++) {
- iAltSymbolNames[i] = new int[altSymbolNames[i].length];
- for(int j=0; j<altSymbolNames[i].length; j++) {
- mAltSymbolNames.put(altSymbolNames[i][j], new Integer(i));
- iAltSymbolNames[i][j] = symbolNameList.indexOf(altSymbolNames[i][j]);
- }
- }
- }
-
+
// lookup
- for(int i = 0; i<symbolCount; i++) {
- symbolAddr[i] = dl.dynamicLookupFunction(symbolNames[i]);
- }
-
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ @Override
+ public Object run() {
+ for(int i = 0; i<symbolCount; i++) {
+ symbolAddr[i] = dl.dynamicLookupFunction(symbolNames[i]);
+ }
+ return null;
+ } } );
+
// validate results
+ boolean res = true;
for(int i = 0; i<symbolCount; i++) {
if( 0 == symbolAddr[i] ) {
// no symbol, check optional and alternative symbols
final String symbol = symbolNames[i];
if ( !optionalSymbolNameSet.contains(symbol) ) {
- // check for API changed symbols
- boolean ok = false;
- final Integer cI = mAltSymbolNames.get(symbol);
- if ( null != cI ) {
- // check whether alternative symbol is available
- final int ci = cI.intValue();
- for(int j=0; !ok && j<iAltSymbolNames[ci].length; j++) {
- final int si = iAltSymbolNames[ci][j];
- ok = 0 != symbolAddr[si];
- if(ok && (true || DEBUG )) { // keep it verbose per default for now ..
- System.err.println("OK: Unresolved symbol <"+symbol+">, but has alternative <"+symbolNames[si]+">");
- }
- }
- }
- if(!ok) {
- System.err.println("Fail: Could not resolve symbol <"+symbolNames[i]+">: not optional, no alternatives.");
- return false;
- }
- } else if(true || DEBUG ) { // keep it verbose per default for now ..
+ System.err.println("Fail: Could not resolve symbol <"+symbolNames[i]+">: not optional, no alternatives.");
+ res = false;
+ } else if(DEBUG) {
System.err.println("OK: Unresolved optional symbol <"+symbolNames[i]+">");
}
}
}
- return initSymbols0(symbolAddr, symbolCount);
+ versions[0] = FFMPEGStaticNatives.getAVVersion(FFMPEGStaticNatives.getAvVersion0(symbolAddr[0]));
+ versions[1] = FFMPEGStaticNatives.getAVVersion(FFMPEGStaticNatives.getAvVersion0(symbolAddr[1]));
+ versions[2] = FFMPEGStaticNatives.getAVVersion(FFMPEGStaticNatives.getAvVersion0(symbolAddr[2]));
+ versions[3] = FFMPEGStaticNatives.getAVVersion(FFMPEGStaticNatives.getAvVersion0(symbolAddr[3]));
+ versions[4] = FFMPEGStaticNatives.getAVVersion(FFMPEGStaticNatives.getAvVersion0(symbolAddr[4]));
+
+ return res;
}
-
+
protected FFMPEGDynamicLibraryBundleInfo() {
}
@Override
- public boolean shallLinkGlobal() { return true; }
+ public final boolean shallLinkGlobal() { return true; }
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Returns <code>true</code>.
+ * </p>
+ */
@Override
- public boolean shallLookupGlobal() { return true; }
-
+ public final boolean shallLookupGlobal() {
+ return true;
+ }
+
@Override
public final List<String> getGlueLibNames() {
return glueLibNames;
}
@Override
- public List<List<String>> getToolLibNames() {
- List<List<String>> libsList = new ArrayList<List<String>>();
+ public final List<List<String>> getToolLibNames() {
+ final List<List<String>> libsList = new ArrayList<List<String>>();
+
+ // 6: util, format, codec, device, avresample, swresample
final List<String> avutil = new ArrayList<String>();
avutil.add("avutil"); // default
- avutil.add("libavutil.so.52"); // dummy future proof
+ avutil.add("libavutil.so.53"); // dummy future proof
+ avutil.add("libavutil.so.52"); // ffmpeg 1.2 + 2 / libav 9 + 10
avutil.add("libavutil.so.51"); // 0.8
avutil.add("libavutil.so.50"); // 0.7
-
- avutil.add("avutil-52"); // dummy future proof
+
+ avutil.add("avutil-53"); // dummy future proof
+ avutil.add("avutil-52"); // ffmpeg 1.2 + 2 / libav 9 + 10
avutil.add("avutil-51"); // 0.8
avutil.add("avutil-50"); // 0.7
libsList.add(avutil);
-
+
final List<String> avformat = new ArrayList<String>();
avformat.add("avformat"); // default
- avformat.add("libavformat.so.55"); // dummy future proof
- avformat.add("libavformat.so.54"); // 0.?
+ avformat.add("libavformat.so.56"); // dummy future proof
+ avformat.add("libavformat.so.55"); // ffmpeg 2 / libav 10
+ avformat.add("libavformat.so.54"); // ffmpeg 1.2 / libav 9
avformat.add("libavformat.so.53"); // 0.8
avformat.add("libavformat.so.52"); // 0.7
-
- avformat.add("avformat-55"); // dummy future proof
- avformat.add("avformat-54"); // 0.?
+
+ avformat.add("avformat-56"); // dummy future proof
+ avformat.add("avformat-55"); // ffmpeg 2 / libav 10
+ avformat.add("avformat-54"); // ffmpeg 1.2 / libav 9
avformat.add("avformat-53"); // 0.8
avformat.add("avformat-52"); // 0.7
libsList.add(avformat);
-
+
final List<String> avcodec = new ArrayList<String>();
avcodec.add("avcodec"); // default
- avcodec.add("libavcodec.so.55"); // dummy future proof
- avcodec.add("libavcodec.so.54"); // 0.?
+ avcodec.add("libavcodec.so.56"); // dummy future proof
+ avcodec.add("libavcodec.so.55"); // ffmpeg 2/ libav 10
+ avcodec.add("libavcodec.so.54"); // ffmpeg 1.2 / libav 9
avcodec.add("libavcodec.so.53"); // 0.8
- avcodec.add("libavcodec.so.52"); // 0.7
-
- avcodec.add("avcodec-55"); // dummy future proof
- avcodec.add("avcodec-54"); // 0.?
+ avcodec.add("libavcodec.so.52"); // 0.7
+
+ avcodec.add("avcodec-56"); // dummy future proof
+ avcodec.add("avcodec-55"); // ffmpeg 2/ libav 10
+ avcodec.add("avcodec-54"); // ffmpeg 1.2 / libav 9
avcodec.add("avcodec-53"); // 0.8
avcodec.add("avcodec-52"); // 0.7
libsList.add(avcodec);
-
+
+ final List<String> avdevice = new ArrayList<String>();
+ avdevice.add("avdevice"); // default
+
+ avdevice.add("libavdevice.so.56"); // dummy future proof
+ avdevice.add("libavdevice.so.55"); // ffmpeg 2
+ avdevice.add("libavdevice.so.54"); // ffmpeg 1.2 / libav 10
+ avdevice.add("libavdevice.so.53"); // 0.8 && libav 9
+
+ avdevice.add("avdevice-56"); // dummy future proof
+ avdevice.add("avdevice-55"); // ffmpeg 2
+ avdevice.add("avdevice-54"); // ffmpeg 1.2 / libav 10
+ avdevice.add("avdevice-53"); // 0.8 && libav 9
+ libsList.add(avdevice);
+
+ final List<String> avresample = new ArrayList<String>();
+ avresample.add("avresample"); // default
+
+ avresample.add("libavresample.so.2"); // dummy future proof
+ avresample.add("libavresample.so.1"); // libav 9 + 10
+
+ avresample.add("avresample-2"); // dummy future proof
+ avresample.add("avresample-1"); // libav 9 + 10
+ libsList.add(avresample);
+
+ final List<String> swresample = new ArrayList<String>();
+ swresample.add("swresample"); // default
+
+ swresample.add("libswresample.so.1"); // dummy future proof
+ swresample.add("libswresample.so.0"); // ffmpeg 1.2 + 2.x
+
+ swresample.add("swresample-1"); // dummy future proof
+ swresample.add("swresample-0"); // ffmpeg 1.2 + 2.x
+ libsList.add(swresample);
+
return libsList;
}
@@ -269,19 +424,17 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo {
}
@Override
- public final long toolGetProcAddress(long toolGetProcAddressHandle, String funcName) {
+ public final long toolGetProcAddress(final long toolGetProcAddressHandle, final String funcName) {
return 0;
}
@Override
- public boolean useToolGetProcAdressFirst(String funcName) {
+ public final boolean useToolGetProcAdressFirst(final String funcName) {
return false;
}
@Override
- public RunnableExecutor getLibLoaderExecutor() {
+ public final RunnableExecutor getLibLoaderExecutor() {
return DynamicLibraryBundle.getDefaultRunnableExecutor();
- }
-
- private static native boolean initSymbols0(long[] symbols, int count);
+ }
}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
index 4be2bcb58..4601df67d 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
@@ -3,14 +3,14 @@
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
- *
+ *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
@@ -20,7 +20,7 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
@@ -29,514 +29,806 @@
package jogamp.opengl.util.av.impl;
import java.io.IOException;
-import java.nio.Buffer;
import java.nio.ByteBuffer;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
import javax.media.opengl.GL;
import javax.media.opengl.GL2ES2;
import javax.media.opengl.GLException;
+import com.jogamp.common.util.IOUtil;
import com.jogamp.common.util.VersionNumber;
import com.jogamp.gluegen.runtime.ProcAddressTable;
+import com.jogamp.opengl.util.TimeFrameI;
import com.jogamp.opengl.util.GLPixelStorageModes;
+import com.jogamp.opengl.util.av.AudioSink;
+import com.jogamp.opengl.util.av.AudioSink.AudioFormat;
+import com.jogamp.opengl.util.av.AudioSinkFactory;
+import com.jogamp.opengl.util.av.GLMediaPlayer;
import com.jogamp.opengl.util.texture.Texture;
-import com.jogamp.opengl.util.texture.TextureSequence;
+import jogamp.common.os.PlatformPropsImpl;
import jogamp.opengl.GLContextImpl;
-import jogamp.opengl.es1.GLES1ProcAddressTable;
-import jogamp.opengl.es2.GLES2ProcAddressTable;
-import jogamp.opengl.gl4.GL4bcProcAddressTable;
-import jogamp.opengl.util.av.EGLMediaPlayerImpl;
+import jogamp.opengl.util.av.AudioSampleFormat;
+import jogamp.opengl.util.av.GLMediaPlayerImpl;
+import jogamp.opengl.util.av.VideoPixelFormat;
/***
* Implementation utilizes <a href="http://libav.org/">Libav</a>
- * or <a href="http://ffmpeg.org/">FFmpeg</a> which is ubiquitous
- * available and usually pre-installed on Unix platforms. Due to legal
- * reasons we cannot deploy binaries of it, which contains patented codecs.
+ * or <a href="http://ffmpeg.org/">FFmpeg</a> which are ubiquitous
+ * available and usually pre-installed on Unix platforms.
+ * <p>
+ * Due to legal reasons we cannot deploy binaries of it, which contains patented codecs.
+ * </p>
+ * <p>
* Besides the default BSD/Linux/.. repositories and installations,
- * precompiled binaries can be found at the listed location below.
+ * precompiled binaries can be found at the
+ * <a href="#libavavail">listed location below</a>.
+ * </p>
+ *
+ * <a name="implspecifics"><h5>Implementation specifics</h5></a>
* <p>
- * Implements YUV420P to RGB fragment shader conversion
- * and the usual packed RGB formats.
- * The decoded video frame is written directly into an OpenGL texture
- * on the GPU in it's native format. A custom fragment shader converts
- * the native pixelformat to a usable RGB format if required.
- * Hence only 1 copy is required before bloating the picture
- * from YUV to RGB, for example.
- * </p>
+ * The decoded video frame is written directly into an OpenGL texture
+ * on the GPU in it's native format. A custom fragment shader converts
+ * the native pixelformat to a usable <i>RGB</i> format if required.
+ * Hence only 1 copy is required before bloating the picture
+ * from <i>YUV*</i> to <i>RGB</i>, for example.
+ * </p>
* <p>
- * Utilizes a slim dynamic and native binding to the Lib_av
+ * Implements pixel format conversion to <i>RGB</i> via
+ * fragment shader texture-lookup functions:
+ * <ul>
+ * <li>{@link VideoPixelFormat#YUV420P}</li>
+ * <li>{@link VideoPixelFormat#YUVJ420P}</li>
+ * <li>{@link VideoPixelFormat#YUV422P}</li>
+ * <li>{@link VideoPixelFormat#YUVJ422P}</li>
+ * <li>{@link VideoPixelFormat#YUYV422}</li>
+ * <li>{@link VideoPixelFormat#BGR24}</li>
+ * </ul>
+ * </p>
+ * <p>
+ *
+ * <a name="libavspecifics"><h5>Libav Specifics</h5></a>
+ * <p>
+ * Utilizes a slim dynamic and native binding to the Lib_av
* libraries:
* <ul>
- * <li>libavutil</li>
- * <li>libavformat</li>
* <li>libavcodec</li>
- * </ul>
+ * <li>libavformat</li>
+ * <li>libavutil</li>
+ * <li>libavresample (opt)</li>
+ * <li>libavdevice (opt)</li>
+ * </ul>
+ * </p>
+ *
+ * <a name="compatibility"><h5>LibAV Compatibility</h5></a>
+ * <p>
+ * Currently we are binary compatible w/:
+ * <table border="1">
+ * <tr><th>libav / ffmpeg</th><th>lavc</th><th>lavf</th><th>lavu</th><th>lavr</th> <th>FFMPEG* class</th></tr>
+ * <tr><td>0.8</td> <td>53</td> <td>53</td> <td>51</td> <td></td> <td>FFMPEGv08</td></tr>
+ * <tr><td>9.0 / 1.2</td> <td>54</td> <td>54</td> <td>52</td> <td>01/00</td> <td>FFMPEGv09</td></tr>
+ * <tr><td>10 / 2</td> <td>55</td> <td>55</td> <td>53/52</td> <td>01/00</td> <td>FFMPEGv10</td></tr>
+ * </table>
* </p>
* <p>
- * http://libav.org/
+ * See http://upstream-tracker.org/versions/libav.html
* </p>
- * <p>
- * Check tag 'FIXME: Add more planar formats !'
+ * <p>
+ * Check tag 'FIXME: Add more planar formats !'
* here and in the corresponding native code
- * <code>jogl/src/jogl/native/ffmpeg/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c</code>
+ * <code>jogl/src/jogl/native/libav/ffmpeg_impl_template.c</code>
* </p>
+ *
+ *
+ * <a name="todo"><h5>TODO:</h5></a>
* <p>
- * TODO:
* <ul>
- * <li>Audio Output</li>
- * <li>Off thread <i>next frame</i> processing using multiple target textures</li>
- * <li>better pts sync handling</li>
- * <li>fix seek</li>
- * </ul>
+ * <li>better audio synchronization handling? (video is synchronized)</li>
+ * </ul>
* </p>
- * Pre-compiled Libav / FFmpeg packages:
+ *
+ * <a name="libavavail"><h5>FFMPEG / LibAV Availability</h5></a>
+ * <p>
* <ul>
- * <li>Windows: http://ffmpeg.zeranoe.com/builds/</li>
- * <li>MacOSX: http://www.ffmpegx.com/</li>
+ * <li>GNU/Linux: ffmpeg or libav are deployed in most distributions.</li>
+ * <li>Windows:
+ * <ul>
+ * <li>http://ffmpeg.zeranoe.com/builds/ (ffmpeg) <i>recommended, works w/ dshow</i></li>
+ * <li>http://win32.libav.org/releases/ (libav)</li>
+ * </ul></li>
+ * <li>MacOSX using Homebrew
+ * <ul>
+ * <li>https://github.com/Homebrew/homebrew/wiki/Installation</li>
+ * <li>https://trac.ffmpeg.org/wiki/CompilationGuide/MacOSX</li>
+ * </ul></li>
* <li>OpenIndiana/Solaris:<pre>
* pkg set-publisher -p http://pkg.openindiana.org/sfe-encumbered.
* pkt install pkg:/video/ffmpeg
* </pre></li>
- * </ul>
+ * </ul>
+ * </p>
*/
-public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl {
- public static final VersionNumber avUtilVersion;
- public static final VersionNumber avFormatVersion;
- public static final VersionNumber avCodecVersion;
- static final boolean available;
-
+public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
+
+ /** POSIX ENOSYS {@value}: Function not implemented. FIXME: Move to GlueGen ?!*/
+ private static final int ENOSYS = 38;
+
+ // Instance data
+ private static final FFMPEGNatives natives;
+ private static final int avUtilMajorVersionCC;
+ private static final int avFormatMajorVersionCC;
+ private static final int avCodecMajorVersionCC;
+ private static final int avResampleMajorVersionCC;
+ private static final int swResampleMajorVersionCC;
+ private static final boolean available;
+
static {
- if(FFMPEGDynamicLibraryBundleInfo.initSingleton()) {
- avUtilVersion = getAVVersion(getAvUtilVersion0());
- avFormatVersion = getAVVersion(getAvFormatVersion0());
- avCodecVersion = getAVVersion(getAvCodecVersion0());
- System.err.println("LIB_AV Util : "+avUtilVersion);
- System.err.println("LIB_AV Format: "+avFormatVersion);
- System.err.println("LIB_AV Codec : "+avCodecVersion);
- available = initIDs0();
+ final boolean libAVGood = FFMPEGDynamicLibraryBundleInfo.initSingleton();
+ final boolean libAVVersionGood;
+ if( FFMPEGDynamicLibraryBundleInfo.libsLoaded() ) {
+ natives = FFMPEGDynamicLibraryBundleInfo.getNatives();
+ if( null != natives ) {
+ avCodecMajorVersionCC = natives.getAvCodecMajorVersionCC0();
+ avFormatMajorVersionCC = natives.getAvFormatMajorVersionCC0();
+ avUtilMajorVersionCC = natives.getAvUtilMajorVersionCC0();
+ avResampleMajorVersionCC = natives.getAvResampleMajorVersionCC0();
+ swResampleMajorVersionCC = natives.getSwResampleMajorVersionCC0();
+ } else {
+ avUtilMajorVersionCC = 0;
+ avFormatMajorVersionCC = 0;
+ avCodecMajorVersionCC = 0;
+ avResampleMajorVersionCC = 0;
+ swResampleMajorVersionCC = 0;
+ }
+ final VersionNumber avCodecVersion = FFMPEGDynamicLibraryBundleInfo.avCodecVersion;
+ final VersionNumber avFormatVersion = FFMPEGDynamicLibraryBundleInfo.avFormatVersion;
+ final VersionNumber avUtilVersion = FFMPEGDynamicLibraryBundleInfo.avUtilVersion;
+ final VersionNumber avResampleVersion = FFMPEGDynamicLibraryBundleInfo.avResampleVersion;
+ final boolean avResampleLoaded = FFMPEGDynamicLibraryBundleInfo.avResampleLoaded();
+ final VersionNumber swResampleVersion = FFMPEGDynamicLibraryBundleInfo.swResampleVersion;
+ final boolean swResampleLoaded = FFMPEGDynamicLibraryBundleInfo.swResampleLoaded();
+ if( DEBUG ) {
+ System.err.println("LIB_AV Codec : "+avCodecVersion+" [cc "+avCodecMajorVersionCC+"]");
+ System.err.println("LIB_AV Format : "+avFormatVersion+" [cc "+avFormatMajorVersionCC+"]");
+ System.err.println("LIB_AV Util : "+avUtilVersion+" [cc "+avUtilMajorVersionCC+"]");
+ System.err.println("LIB_AV Resample: "+avResampleVersion+" [cc "+avResampleMajorVersionCC+", loaded "+avResampleLoaded+"]");
+ System.err.println("LIB_SW Resample: "+swResampleVersion+" [cc "+swResampleMajorVersionCC+", loaded "+swResampleLoaded+"]");
+ System.err.println("LIB_AV Device : [loaded "+FFMPEGDynamicLibraryBundleInfo.avDeviceLoaded()+"]");
+ System.err.println("LIB_AV Class : "+(null!= natives ? natives.getClass().getSimpleName() : "n/a"));
+ }
+ final int avCodecMajor = avCodecVersion.getMajor();
+ final int avFormatMajor = avFormatVersion.getMajor();
+ final int avUtilMajor = avUtilVersion.getMajor();
+ libAVVersionGood = avCodecMajorVersionCC == avCodecMajor &&
+ avFormatMajorVersionCC == avFormatMajor &&
+ ( avUtilMajorVersionCC == avUtilMajor ||
+ 55 == avCodecMajorVersionCC && 53 == avUtilMajorVersionCC && 52 == avUtilMajor /* ffmpeg 2.x */
+ ) &&
+ ( !avResampleLoaded || avResampleMajorVersionCC < 0 || avResampleMajorVersionCC == avResampleVersion.getMajor() ) &&
+ ( !swResampleLoaded || swResampleMajorVersionCC < 0 || swResampleMajorVersionCC == swResampleVersion.getMajor() ) ;
+ if( !libAVVersionGood ) {
+ System.err.println("LIB_AV Not Matching Compile-Time / Runtime Major-Version");
+ }
} else {
- avUtilVersion = null;
- avFormatVersion = null;
- avCodecVersion = null;
- available = false;
+ natives = null;
+ avUtilMajorVersionCC = 0;
+ avFormatMajorVersionCC = 0;
+ avCodecMajorVersionCC = 0;
+ avResampleMajorVersionCC = 0;
+ swResampleMajorVersionCC = 0;
+ libAVVersionGood = false;
}
+ available = libAVGood && libAVVersionGood && null != natives;
}
-
+
public static final boolean isAvailable() { return available; }
- private static VersionNumber getAVVersion(int vers) {
- return new VersionNumber( ( vers >> 16 ) & 0xFF,
- ( vers >> 8 ) & 0xFF,
- ( vers >> 0 ) & 0xFF );
- }
-
- protected long moviePtr = 0;
- protected long procAddrGLTexSubImage2D = 0;
- protected EGLMediaPlayerImpl.EGLTextureFrame lastTex = null;
- protected GLPixelStorageModes psm;
- protected PixelFormat vPixelFmt = null;
- protected int vPlanes = 0;
- protected int vBitsPerPixel = 0;
- protected int vBytesPerPixelPerPlane = 0;
- protected int[] vLinesize = { 0, 0, 0 }; // per plane
- protected int[] vTexWidth = { 0, 0, 0 }; // per plane
- protected int texWidth, texHeight; // overall (stuffing planes in one texture)
- protected ByteBuffer texCopy;
+ //
+ // General
+ //
+
+ private long moviePtr = 0;
+
+ //
+ // Video
+ //
+
+ private String texLookupFuncName = "ffmpegTexture2D";
+ private boolean usesTexLookupShader = false;
+ private VideoPixelFormat vPixelFmt = null;
+ private int vPlanes = 0;
+ private int vBitsPerPixel = 0;
+ private int vBytesPerPixelPerPlane = 0;
+ private int texWidth, texHeight; // overall (stuffing planes in one texture)
+ private String singleTexComp = "r";
+ private final GLPixelStorageModes psm;
+
+ //
+ // Audio
+ //
+
+ private AudioSink.AudioFormat avChosenAudioFormat;
+ private int audioSamplesPerFrameAndChannel = 0;
public FFMPEGMediaPlayer() {
- super(TextureType.GL, false);
if(!available) {
throw new RuntimeException("FFMPEGMediaPlayer not available");
}
- setTextureCount(1);
- moviePtr = createInstance0(true);
+ moviePtr = natives.createInstance0(this, DEBUG_NATIVE);
if(0==moviePtr) {
throw new GLException("Couldn't create FFMPEGInstance");
}
psm = new GLPixelStorageModes();
+ audioSink = null;
}
-
+
@Override
- protected TextureSequence.TextureFrame createTexImage(GL gl, int idx, int[] tex) {
- if(TextureType.GL == texType) {
- final Texture texture = super.createTexImageImpl(gl, idx, tex, texWidth, texHeight, true);
- lastTex = new EGLTextureFrame(null, texture, 0, 0);
- } else {
- throw new InternalError("n/a");
+ protected final void destroyImpl(final GL gl) {
+ if (moviePtr != 0) {
+ natives.destroyInstance0(moviePtr);
+ moviePtr = 0;
}
- return lastTex;
+ destroyAudioSink();
}
-
- @Override
- protected void destroyTexImage(GL gl, TextureSequence.TextureFrame imgTex) {
- lastTex = null;
- super.destroyTexImage(gl, imgTex);
+ private final void destroyAudioSink() {
+ final AudioSink _audioSink = audioSink;
+ if( null != _audioSink ) {
+ audioSink = null;
+ _audioSink.destroy();
+ }
}
-
+
+ public static final String dev_video_linux = "/dev/video";
+
@Override
- protected void destroyImpl(GL gl) {
- if (moviePtr != 0) {
- destroyInstance0(moviePtr);
- moviePtr = 0;
+ protected final void initStreamImpl(final int vid, final int aid) throws IOException {
+ if(0==moviePtr) {
+ throw new GLException("FFMPEG native instance null");
}
+ if(DEBUG) {
+ System.err.println("initStream: p1 "+this);
+ }
+
+ final String streamLocS = IOUtil.getUriFilePathOrASCII(getUri());
+ destroyAudioSink();
+ if( GLMediaPlayer.STREAM_ID_NONE == aid ) {
+ audioSink = AudioSinkFactory.createNull();
+ } else {
+ audioSink = AudioSinkFactory.createDefault();
+ }
+ final AudioFormat preferredAudioFormat = audioSink.getPreferredFormat();
+ if(DEBUG) {
+ System.err.println("initStream: p2 preferred "+preferredAudioFormat+", "+this);
+ }
+
+ final boolean isCameraInput = null != cameraPath;
+ final String resStreamLocS;
+ // int rw=640, rh=480, rr=15;
+ int rw=-1, rh=-1, rr=-1;
+ String sizes = null;
+ if( isCameraInput ) {
+ switch(PlatformPropsImpl.OS_TYPE) {
+ case ANDROID:
+ // ??
+ case FREEBSD:
+ case HPUX:
+ case LINUX:
+ case SUNOS:
+ resStreamLocS = dev_video_linux + cameraPath.decode();
+ break;
+ case WINDOWS:
+ resStreamLocS = cameraPath.decode();
+ break;
+ case MACOS:
+ case OPENKODE:
+ default:
+ resStreamLocS = streamLocS; // FIXME: ??
+ break;
+ }
+ if( null != cameraProps ) {
+ sizes = cameraProps.get(CameraPropSizeS);
+ int v = getPropIntVal(cameraProps, CameraPropWidth);
+ if( v > 0 ) { rw = v; }
+ v = getPropIntVal(cameraProps, CameraPropHeight);
+ if( v > 0 ) { rh = v; }
+ v = getPropIntVal(cameraProps, CameraPropRate);
+ if( v > 0 ) { rr = v; }
+ }
+ } else {
+ resStreamLocS = streamLocS;
+ }
+ final int aMaxChannelCount = audioSink.getMaxSupportedChannels();
+ final int aPrefSampleRate = preferredAudioFormat.sampleRate;
+ // setStream(..) issues updateAttributes*(..), and defines avChosenAudioFormat, vid, aid, .. etc
+ if(DEBUG) {
+ System.err.println("initStream: p3 cameraPath "+cameraPath+", isCameraInput "+isCameraInput);
+ System.err.println("initStream: p3 stream "+getUri()+" -> "+streamLocS+" -> "+resStreamLocS);
+ System.err.println("initStream: p3 vid "+vid+", sizes "+sizes+", reqVideo "+rw+"x"+rh+"@"+rr+", aid "+aid+", aMaxChannelCount "+aMaxChannelCount+", aPrefSampleRate "+aPrefSampleRate);
+ }
+ natives.setStream0(moviePtr, resStreamLocS, isCameraInput, vid, sizes, rw, rh, rr, aid, aMaxChannelCount, aPrefSampleRate);
}
-
+
@Override
- protected void initGLStreamImpl(GL gl, int[] texNames) throws IOException {
+ protected final void initGLImpl(final GL gl) throws IOException, GLException {
if(0==moviePtr) {
throw new GLException("FFMPEG native instance null");
}
- final String urlS=urlConn.getURL().toExternalForm();
-
- System.out.println("setURL: p1 "+this);
- setStream0(moviePtr, urlS, -1, -1);
- System.out.println("setURL: p2 "+this);
- int tf, tif=GL.GL_RGBA; // texture format and internal format
- switch(vBytesPerPixelPerPlane) {
- case 1: tf = GL2ES2.GL_ALPHA; tif=GL.GL_ALPHA; break;
- case 3: tf = GL2ES2.GL_RGB; tif=GL.GL_RGB; break;
- case 4: tf = GL2ES2.GL_RGBA; tif=GL.GL_RGBA; break;
- default: throw new RuntimeException("Unsupported bytes-per-pixel / plane "+vBytesPerPixelPerPlane);
- }
- setTextureFormat(tif, tf);
- setTextureType(GL.GL_UNSIGNED_BYTE);
- GLContextImpl ctx = (GLContextImpl)gl.getContext();
- ProcAddressTable pt = ctx.getGLProcAddressTable();
- if(pt instanceof GLES2ProcAddressTable) {
- procAddrGLTexSubImage2D = ((GLES2ProcAddressTable)pt)._addressof_glTexSubImage2D;
- } else if(pt instanceof GLES1ProcAddressTable) {
- procAddrGLTexSubImage2D = ((GLES1ProcAddressTable)pt)._addressof_glTexSubImage2D;
- } else if(pt instanceof GL4bcProcAddressTable) {
- procAddrGLTexSubImage2D = ((GL4bcProcAddressTable)pt)._addressof_glTexSubImage2D;
+ if(null == audioSink) {
+ throw new GLException("AudioSink null");
+ }
+ final int audioQueueLimit;
+ if( null != gl && STREAM_ID_NONE != getVID() ) {
+ final GLContextImpl ctx = (GLContextImpl)gl.getContext();
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ @Override
+ public Object run() {
+ final ProcAddressTable pt = ctx.getGLProcAddressTable();
+ final long procAddrGLTexSubImage2D = pt.getAddressFor("glTexSubImage2D");
+ final long procAddrGLGetError = pt.getAddressFor("glGetError");
+ final long procAddrGLFlush = pt.getAddressFor("glFlush");
+ final long procAddrGLFinish = pt.getAddressFor("glFinish");
+ natives.setGLFuncs0(moviePtr, procAddrGLTexSubImage2D, procAddrGLGetError, procAddrGLFlush, procAddrGLFinish);
+ return null;
+ } } );
+ audioQueueLimit = AudioSink.DefaultQueueLimitWithVideo;
+ } else {
+ audioQueueLimit = AudioSink.DefaultQueueLimitAudioOnly;
+ }
+ if(DEBUG) {
+ System.err.println("initGL: p3 avChosen "+avChosenAudioFormat);
+ }
+
+ if( STREAM_ID_NONE == getAID() ) {
+ audioSink.destroy();
+ audioSink = AudioSinkFactory.createNull();
+ audioSink.init(AudioSink.DefaultFormat, 0, AudioSink.DefaultInitialQueueSize, AudioSink.DefaultQueueGrowAmount, audioQueueLimit);
} else {
- throw new InternalError("Unknown ProcAddressTable: "+pt.getClass().getName()+" of "+ctx.getClass().getName());
+ final float frameDuration;
+ if( audioSamplesPerFrameAndChannel > 0 ) {
+ frameDuration= avChosenAudioFormat.getSamplesDuration(audioSamplesPerFrameAndChannel);
+ } else {
+ frameDuration = AudioSink.DefaultFrameDuration;
+ }
+ final boolean audioSinkOK = audioSink.init(avChosenAudioFormat, frameDuration, AudioSink.DefaultInitialQueueSize, AudioSink.DefaultQueueGrowAmount, audioQueueLimit);
+ if( !audioSinkOK ) {
+ System.err.println("AudioSink "+audioSink.getClass().getName()+" does not support "+avChosenAudioFormat+", using Null");
+ audioSink.destroy();
+ audioSink = AudioSinkFactory.createNull();
+ audioSink.init(avChosenAudioFormat, 0, AudioSink.DefaultInitialQueueSize, AudioSink.DefaultQueueGrowAmount, audioQueueLimit);
+ }
+ }
+ if(DEBUG) {
+ System.err.println("initGL: p4 chosen "+avChosenAudioFormat);
+ System.err.println("initGL: p4 chosen "+audioSink);
+ }
+
+ if( null != gl && STREAM_ID_NONE != getVID() ) {
+ int tf, tif=GL.GL_RGBA; // texture format and internal format
+ final int tt = GL.GL_UNSIGNED_BYTE;
+ switch(vBytesPerPixelPerPlane) {
+ case 1:
+ if( gl.isGL3ES3() ) {
+ // RED is supported on ES3 and >= GL3 [core]; ALPHA is deprecated on core
+ tf = GL2ES2.GL_RED; tif=GL2ES2.GL_RED; singleTexComp = "r";
+ } else {
+ // ALPHA is supported on ES2 and GL2, i.e. <= GL3 [core] or compatibility
+ tf = GL.GL_ALPHA; tif=GL.GL_ALPHA; singleTexComp = "a";
+ }
+ break;
+
+ case 2: if( vPixelFmt == VideoPixelFormat.YUYV422 ) {
+ // YUYV422: // < packed YUV 4:2:2, 2x 16bpp, Y0 Cb Y1 Cr
+ // Stuffed into RGBA half width texture
+ tf = GL.GL_RGBA; tif=GL.GL_RGBA; break;
+ } else {
+ tf = GL2ES2.GL_RG; tif=GL2ES2.GL_RG; break;
+ }
+ case 3: tf = GL.GL_RGB; tif=GL.GL_RGB; break;
+ case 4: if( vPixelFmt == VideoPixelFormat.BGRA ) {
+ tf = GL.GL_BGRA; tif=GL.GL_RGBA; break;
+ } else {
+ tf = GL.GL_RGBA; tif=GL.GL_RGBA; break;
+ }
+ default: throw new RuntimeException("Unsupported bytes-per-pixel / plane "+vBytesPerPixelPerPlane);
+ }
+ setTextureFormat(tif, tf);
+ setTextureType(tt);
+ if(DEBUG) {
+ System.err.println("initGL: p5: video "+vPixelFmt+", planes "+vPlanes+", bpp "+vBitsPerPixel+"/"+vBytesPerPixelPerPlane+
+ ", tex "+texWidth+"x"+texHeight+", usesTexLookupShader "+usesTexLookupShader);
+ }
}
}
- private void updateAttributes2(int pixFmt, int planes, int bitsPerPixel, int bytesPerPixelPerPlane,
- int lSz0, int lSz1, int lSz2,
- int tWd0, int tWd1, int tWd2) {
- vPixelFmt = PixelFormat.valueOf(pixFmt);
- vPlanes = planes;
- vBitsPerPixel = bitsPerPixel;
- vBytesPerPixelPerPlane = bytesPerPixelPerPlane;
- vLinesize[0] = lSz0; vLinesize[1] = lSz1; vLinesize[2] = lSz2;
- vTexWidth[0] = tWd0; vTexWidth[1] = tWd1; vTexWidth[2] = tWd2;
-
- switch(vPixelFmt) {
- case YUV420P:
- // YUV420P: Adding U+V on right side of fixed height texture,
- // since width is already aligned by decoder.
- // Y=w*h, Y=w/2*h/2, U=w/2*h/2
- // w*h + 2 ( w/2 * h/2 )
- // w*h + w*h/2
- // 2*w/2 * h
- texWidth = vTexWidth[0] + vTexWidth[1]; texHeight = height;
+ @Override
+ protected final TextureFrame createTexImage(final GL gl, final int texName) {
+ return new TextureFrame( createTexImageImpl(gl, texName, texWidth, texHeight) );
+ }
+
+ /**
+ * @param sampleRate sample rate in Hz (1/s)
+ * @param sampleSize sample size in bits
+ * @param channelCount number of channels
+ * @param signed true if signed number, false for unsigned
+ * @param fixedP true for fixed point value, false for unsigned floating point value with a sampleSize of 32 (float) or 64 (double)
+ * @param planar true for planar data package (each channel in own data buffer), false for packed data channels interleaved in one buffer.
+ * @param littleEndian true for little-endian, false for big endian
+ * @return
+ */
+
+ /**
+ * Native callback
+ * Converts the given libav/ffmpeg values to {@link AudioFormat} and returns {@link AudioSink#isSupported(AudioFormat)}.
+ * @param audioSampleFmt ffmpeg/libav audio-sample-format, see {@link AudioSampleFormat}.
+ * @param audioSampleRate sample rate in Hz (1/s)
+ * @param audioChannels number of channels
+ */
+ final boolean isAudioFormatSupported(final int audioSampleFmt, final int audioSampleRate, final int audioChannels) {
+ final AudioSampleFormat avFmt = AudioSampleFormat.valueOf(audioSampleFmt);
+ final AudioFormat audioFormat = avAudioFormat2Local(avFmt, audioSampleRate, audioChannels);
+ final boolean res = audioSink.isSupported(audioFormat);
+ if( DEBUG ) {
+ System.err.println("AudioSink.isSupported: "+res+": av[fmt "+avFmt+", rate "+audioSampleRate+", chan "+audioChannels+"] -> "+audioFormat);
+ }
+ return res;
+ }
+
+ /**
+ * Returns {@link AudioFormat} as converted from the given libav/ffmpeg values.
+ * @param audioSampleFmt ffmpeg/libav audio-sample-format, see {@link AudioSampleFormat}.
+ * @param audioSampleRate sample rate in Hz (1/s)
+ * @param audioChannels number of channels
+ */
+ private final AudioFormat avAudioFormat2Local(final AudioSampleFormat audioSampleFmt, final int audioSampleRate, final int audioChannels) {
+ final int sampleSize;
+ boolean planar = true;
+ boolean fixedP = true;
+ final boolean signed;
+ switch( audioSampleFmt ) {
+ case S32:
+ planar = false;
+ case S32P:
+ sampleSize = 32;
+ signed = true;
+ break;
+ case S16:
+ planar = false;
+ case S16P:
+ sampleSize = 16;
+ signed = true;
+ break;
+ case U8:
+ planar = false;
+ case U8P:
+ sampleSize = 8;
+ signed = false;
+ break;
+ case DBL:
+ planar = false;
+ case DBLP:
+ sampleSize = 64;
+ signed = true;
+ fixedP = false;
break;
- // case PIX_FMT_YUYV422:
- case RGB24:
- case BGR24:
- case ARGB:
- case RGBA:
- case ABGR:
- case BGRA:
- texWidth = vTexWidth[0]; texHeight = height;
+ case FLT:
+ planar = false;
+ case FLTP:
+ sampleSize = 32;
+ signed = true;
+ fixedP = false;
break;
- default: // FIXME: Add more planar formats !
- throw new RuntimeException("Unsupported pixelformat: "+vPixelFmt);
+ default: // FIXME: Add more formats !
+ throw new IllegalArgumentException("Unsupported sampleformat: "+audioSampleFmt);
+ }
+ return new AudioFormat(audioSampleRate, sampleSize, audioChannels, signed, fixedP, planar, true /* littleEndian */);
+ }
+
+ /**
+ * Native callback
+ * @param vid
+ * @param pixFmt
+ * @param planes
+ * @param bitsPerPixel
+ * @param bytesPerPixelPerPlane
+ * @param tWd0
+ * @param tWd1
+ * @param tWd2
+ * @param aid
+ * @param audioSampleFmt
+ * @param audioSampleRate
+ * @param audioChannels
+ * @param audioSamplesPerFrameAndChannel in audio samples per frame and channel
+ */
+ void setupFFAttributes(final int vid, final int pixFmt, final int planes, final int bitsPerPixel, final int bytesPerPixelPerPlane,
+ final int tWd0, final int tWd1, final int tWd2, final int vW, final int vH,
+ final int aid, final int audioSampleFmt, final int audioSampleRate,
+ final int audioChannels, final int audioSamplesPerFrameAndChannel) {
+ // defaults ..
+ vPixelFmt = null;
+ vPlanes = 0;
+ vBitsPerPixel = 0;
+ vBytesPerPixelPerPlane = 0;
+ usesTexLookupShader = false;
+ texWidth = 0; texHeight = 0;
+
+ final int[] vTexWidth = { 0, 0, 0 }; // per plane
+
+ if( STREAM_ID_NONE != vid ) {
+ vPixelFmt = VideoPixelFormat.valueOf(pixFmt);
+ vPlanes = planes;
+ vBitsPerPixel = bitsPerPixel;
+ vBytesPerPixelPerPlane = bytesPerPixelPerPlane;
+ vTexWidth[0] = tWd0; vTexWidth[1] = tWd1; vTexWidth[2] = tWd2;
+
+ switch(vPixelFmt) {
+ case YUVJ420P:
+ case YUV420P: // < planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
+ usesTexLookupShader = true;
+ // YUV420P: Adding U+V on right side of fixed height texture,
+ // since width is already aligned by decoder.
+ // Splitting texture to 4 quadrants:
+ // Y covers left top/low quadrant
+ // U on top-right quadrant.
+ // V on low-right quadrant.
+ // Y=w*h, U=w/2*h/2, V=w/2*h/2
+ // w*h + 2 ( w/2 * h/2 )
+ // w*h + w*h/2
+ texWidth = vTexWidth[0] + vTexWidth[1]; texHeight = vH;
+ break;
+ case YUVJ422P:
+ case YUV422P:
+ usesTexLookupShader = true;
+ // YUV422P: Adding U+V on right side of fixed height texture,
+ // since width is already aligned by decoder.
+ // Splitting texture to 4 columns
+ // Y covers columns 1+2
+ // U covers columns 3
+ // V covers columns 4
+ texWidth = vTexWidth[0] + vTexWidth[1] + vTexWidth[2]; texHeight = vH;
+ break;
+ case YUYV422: // < packed YUV 4:2:2, 2x 16bpp, Y0 Cb Y1 Cr - stuffed into RGBA half width texture
+ case BGR24:
+ usesTexLookupShader = true;
+ texWidth = vTexWidth[0]; texHeight = vH;
+ break;
+
+ case RGB24:
+ case ARGB:
+ case RGBA:
+ case ABGR:
+ case BGRA:
+ usesTexLookupShader = false;
+ texWidth = vTexWidth[0]; texHeight = vH;
+ break;
+ default: // FIXME: Add more formats !
+ throw new RuntimeException("Unsupported pixelformat: "+vPixelFmt);
+ }
}
+
+ // defaults ..
+ final AudioSampleFormat aSampleFmt;
+ avChosenAudioFormat = null;;
+ this.audioSamplesPerFrameAndChannel = 0;
+
+ if( STREAM_ID_NONE != aid ) {
+ aSampleFmt = AudioSampleFormat.valueOf(audioSampleFmt);
+ avChosenAudioFormat = avAudioFormat2Local(aSampleFmt, audioSampleRate, audioChannels);
+ this.audioSamplesPerFrameAndChannel = audioSamplesPerFrameAndChannel;
+ } else {
+ aSampleFmt = null;
+ }
+
if(DEBUG) {
- System.err.println("XXX0: fmt "+vPixelFmt+", planes "+vPlanes+", bpp "+vBitsPerPixel+"/"+vBytesPerPixelPerPlane);
+ System.err.println("audio: id "+aid+", fmt "+aSampleFmt+", "+avChosenAudioFormat+", aFrameSize/fc "+audioSamplesPerFrameAndChannel);
+ System.err.println("video: id "+vid+", fmt "+vW+"x"+vH+", "+vPixelFmt+", planes "+vPlanes+", bpp "+vBitsPerPixel+"/"+vBytesPerPixelPerPlane+", usesTexLookupShader "+usesTexLookupShader);
for(int i=0; i<3; i++) {
- System.err.println("XXX0 "+i+": "+vTexWidth[i]+"/"+vLinesize[i]);
+ System.err.println("video: p["+i+"]: "+vTexWidth[i]);
}
- System.err.println("XXX0 total tex "+texWidth+"x"+texHeight);
+ System.err.println("video: total tex "+texWidth+"x"+texHeight);
+ System.err.println(this.toString());
}
}
-
+
+ /**
+ * Native callback
+ * @param isInGLOrientation
+ * @param pixFmt
+ * @param planes
+ * @param bitsPerPixel
+ * @param bytesPerPixelPerPlane
+ * @param tWd0
+ * @param tWd1
+ * @param tWd2
+ */
+ void updateVidAttributes(final boolean isInGLOrientation, final int pixFmt, final int planes, final int bitsPerPixel, final int bytesPerPixelPerPlane,
+ final int tWd0, final int tWd1, final int tWd2, final int vW, final int vH) {
+ }
+
/**
* {@inheritDoc}
- *
+ *
* If this implementation generates a specialized shader,
* it allows the user to override the default function name <code>ffmpegTexture2D</code>.
* Otherwise the call is delegated to it's super class.
*/
@Override
- public String getTextureLookupFunctionName(String desiredFuncName) throws IllegalStateException {
- if(State.Uninitialized == state) {
+ public final String getTextureLookupFunctionName(final String desiredFuncName) throws IllegalStateException {
+ if( State.Uninitialized == getState() ) {
throw new IllegalStateException("Instance not initialized: "+this);
}
- if(PixelFormat.YUV420P == vPixelFmt) {
+ if( usesTexLookupShader ) {
if(null != desiredFuncName && desiredFuncName.length()>0) {
- textureLookupFunctionName = desiredFuncName;
+ texLookupFuncName = desiredFuncName;
}
- return textureLookupFunctionName;
+ return texLookupFuncName;
}
- return super.getTextureLookupFunctionName(desiredFuncName);
+ return super.getTextureLookupFunctionName(desiredFuncName);
}
- private String textureLookupFunctionName = "ffmpegTexture2D";
-
+
/**
* {@inheritDoc}
- *
+ *
* Depending on the pixelformat, a specific conversion shader is being created,
- * e.g. YUV420P to RGB. Otherwise the call is delegated to it's super class.
- */
+ * e.g. YUV420P to RGB. Otherwise the call is delegated to it's super class.
+ */
@Override
- public String getTextureLookupFragmentShaderImpl() throws IllegalStateException {
- if(State.Uninitialized == state) {
+ public final String getTextureLookupFragmentShaderImpl() throws IllegalStateException {
+ if( State.Uninitialized == getState() ) {
throw new IllegalStateException("Instance not initialized: "+this);
}
+ if( !usesTexLookupShader ) {
+ return super.getTextureLookupFragmentShaderImpl();
+ }
final float tc_w_1 = (float)getWidth() / (float)texWidth;
switch(vPixelFmt) {
- case YUV420P:
- return
- "vec4 "+textureLookupFunctionName+"(in "+getTextureSampler2DType()+" image, in vec2 texCoord) {\n"+
- " vec2 u_off = vec2("+tc_w_1+", 0.0);\n"+
- " vec2 v_off = vec2("+tc_w_1+", 0.5);\n"+
+ case YUVJ420P:
+ case YUV420P: // < planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
+ return
+ "vec4 "+texLookupFuncName+"(in "+getTextureSampler2DType()+" image, in vec2 texCoord) {\n"+
+ " const vec2 u_off = vec2("+tc_w_1+", 0.0);\n"+
+ " const vec2 v_off = vec2("+tc_w_1+", 0.5);\n"+
" vec2 tc_half = texCoord*0.5;\n"+
" float y,u,v,r,g,b;\n"+
- " y = texture2D(image, texCoord).a;\n"+
- " u = texture2D(image, u_off+tc_half).a;\n"+
- " v = texture2D(image, v_off+tc_half).a;\n"+
+ " y = texture2D(image, texCoord)."+singleTexComp+";\n"+
+ " u = texture2D(image, u_off+tc_half)."+singleTexComp+";\n"+
+ " v = texture2D(image, v_off+tc_half)."+singleTexComp+";\n"+
" y = 1.1643*(y-0.0625);\n"+
" u = u-0.5;\n"+
" v = v-0.5;\n"+
" r = y+1.5958*v;\n"+
" g = y-0.39173*u-0.81290*v;\n"+
- " b = y+2.017*u;\n"+
+ " b = y+2.017*u;\n"+
" return vec4(r, g, b, 1);\n"+
"}\n"
- ;
- default: // FIXME: Add more planar formats !
- return super.getTextureLookupFragmentShaderImpl();
- }
- }
-
- @Override
- protected synchronized int getCurrentPositionImpl() {
- return 0!=moviePtr ? getVideoPTS0(moviePtr) : 0;
- }
+ ;
- @Override
- protected synchronized boolean setPlaySpeedImpl(float rate) {
- return true;
+ case YUVJ422P:
+ case YUV422P: ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)
+ return
+ "vec4 "+texLookupFuncName+"(in "+getTextureSampler2DType()+" image, in vec2 texCoord) {\n"+
+ " const vec2 u_off = vec2("+tc_w_1+" , 0.0);\n"+
+ " const vec2 v_off = vec2("+tc_w_1+" * 1.5, 0.0);\n"+
+ " vec2 tc_halfw = vec2(texCoord.x*0.5, texCoord.y);\n"+
+ " float y,u,v,r,g,b;\n"+
+ " y = texture2D(image, texCoord)."+singleTexComp+";\n"+
+ " u = texture2D(image, u_off+tc_halfw)."+singleTexComp+";\n"+
+ " v = texture2D(image, v_off+tc_halfw)."+singleTexComp+";\n"+
+ " y = 1.1643*(y-0.0625);\n"+
+ " u = u-0.5;\n"+
+ " v = v-0.5;\n"+
+ " r = y+1.5958*v;\n"+
+ " g = y-0.39173*u-0.81290*v;\n"+
+ " b = y+2.017*u;\n"+
+ " return vec4(r, g, b, 1);\n"+
+ "}\n"
+ ;
+
+ case YUYV422: // < packed YUV 4:2:2, 2 x 16bpp, [Y0 Cb] [Y1 Cr]
+ // Stuffed into RGBA half width texture
+ return
+ "vec4 "+texLookupFuncName+"(in "+getTextureSampler2DType()+" image, in vec2 texCoord) {\n"+
+ " "+
+ " float y1,u,y2,v,y,r,g,b;\n"+
+ " vec2 tc_halfw = vec2(texCoord.x*0.5, texCoord.y);\n"+
+ " vec4 yuyv = texture2D(image, tc_halfw).rgba;\n"+
+ " y1 = yuyv.r;\n"+
+ " u = yuyv.g;\n"+
+ " y2 = yuyv.b;\n"+
+ " v = yuyv.a;\n"+
+ " y = mix( y1, y2, mod(gl_FragCoord.x, 2) ); /* avoid branching! */\n"+
+ " y = 1.1643*(y-0.0625);\n"+
+ " u = u-0.5;\n"+
+ " v = v-0.5;\n"+
+ " r = y+1.5958*v;\n"+
+ " g = y-0.39173*u-0.81290*v;\n"+
+ " b = y+2.017*u;\n"+
+ " return vec4(r, g, b, 1);\n"+
+ "}\n"
+ ;
+ case BGR24:
+ return
+ "vec4 "+texLookupFuncName+"(in "+getTextureSampler2DType()+" image, in vec2 texCoord) {\n"+
+ " "+
+ " vec3 bgr = texture2D(image, texCoord).rgb;\n"+
+ " return vec4(bgr.b, bgr.g, bgr.r, 1);\n"+ /* just swizzle */
+ "}\n"
+ ;
+
+ default: // FIXME: Add more formats !
+ throw new InternalError("Add proper mapping of: vPixelFmt "+vPixelFmt+", usesTexLookupShader "+usesTexLookupShader);
+ }
}
@Override
- public synchronized boolean startImpl() {
+ public final boolean playImpl() {
if(0==moviePtr) {
return false;
}
+ final int errno = natives.play0(moviePtr);
+ if( DEBUG_NATIVE && errno != 0 && errno != -ENOSYS) {
+ System.err.println("libav play err: "+errno);
+ }
return true;
}
- /** @return time position after issuing the command */
@Override
- public synchronized boolean pauseImpl() {
+ public final boolean pauseImpl() {
if(0==moviePtr) {
return false;
}
+ final int errno = natives.pause0(moviePtr);
+ if( DEBUG_NATIVE && errno != 0 && errno != -ENOSYS) {
+ System.err.println("libav pause err: "+errno);
+ }
return true;
}
- /** @return time position after issuing the command */
@Override
- public synchronized boolean stopImpl() {
+ protected final synchronized int seekImpl(final int msec) {
if(0==moviePtr) {
- return false;
+ throw new GLException("FFMPEG native instance null");
}
- return true;
+ return natives.seek0(moviePtr, msec);
}
- /** @return time position after issuing the command */
@Override
- protected synchronized int seekImpl(int msec) {
- if(0==moviePtr) {
- throw new GLException("FFMPEG native instance null");
- }
- int pts0 = getVideoPTS0(moviePtr);
- int pts1 = seek0(moviePtr, msec);
- System.err.println("Seek: "+pts0+" -> "+msec+" : "+pts1);
- return pts1;
+ protected void preNextTextureImpl(final GL gl) {
+ psm.setUnpackAlignment(gl, 1); // RGBA ? 4 : 1
+ gl.glActiveTexture(GL.GL_TEXTURE0+getTextureUnit());
}
@Override
- protected TextureSequence.TextureFrame getLastTextureImpl() {
- return lastTex;
+ protected void postNextTextureImpl(final GL gl) {
+ psm.restore(gl);
}
-
- private long lastVideoTime = 0;
- private int lastVideoPTS = 0;
- private static final int dt_d = 9;
-
+
@Override
- protected TextureSequence.TextureFrame getNextTextureImpl(GL gl, boolean blocking) {
+ protected final int getNextTextureImpl(final GL gl, final TextureFrame nextFrame) {
if(0==moviePtr) {
throw new GLException("FFMPEG native instance null");
- }
- if(null != lastTex) {
- psm.setUnpackAlignment(gl, 1); // RGBA ? 4 : 1
- try {
- final Texture tex = lastTex.getTexture();
- gl.glActiveTexture(GL.GL_TEXTURE0+getTextureUnit());
- tex.enable(gl);
- tex.bind(gl);
- readNextPacket0(moviePtr, procAddrGLTexSubImage2D, textureTarget, textureFormat, textureType);
- } finally {
- psm.restore(gl);
- }
- final int pts = getVideoPTS0(moviePtr); // this frame
- if(blocking) {
- // poor mans video sync .. TODO: off thread 'readNextPackage0(..)' on shared GLContext and multi textures/unit!
- final long now = System.currentTimeMillis();
- final long now_d = now - lastVideoTime;
- final long pts_d = pts - lastVideoPTS;
- final long dt = (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ;
- lastVideoTime = now;
- // System.err.println("s: pts-v "+pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt);
- if(dt>dt_d) {
- try {
- Thread.sleep(dt-dt_d);
- } catch (InterruptedException e) { }
- } /* else if(0>pts_d) {
- System.err.println("s: pts-v "+pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt);
- } */
- }
- lastVideoPTS = pts;
}
- return lastTex;
- }
-
- private void consumeAudio(int len) {
-
+ int vPTS = TimeFrameI.INVALID_PTS;
+ if( null != gl ) {
+ final Texture tex = nextFrame.getTexture();
+ tex.enable(gl);
+ tex.bind(gl);
+ }
+
+ /** Try decode up to 10 packets to find one containing video. */
+ for(int i=0; TimeFrameI.INVALID_PTS == vPTS && 10 > i; i++) {
+ vPTS = natives.readNextPacket0(moviePtr, getTextureTarget(), getTextureFormat(), getTextureType());
+ }
+ if( null != nextFrame ) {
+ nextFrame.setPTS(vPTS);
+ }
+ return vPTS;
}
-
- private static native int getAvUtilVersion0();
- private static native int getAvFormatVersion0();
- private static native int getAvCodecVersion0();
- private static native boolean initIDs0();
- private native long createInstance0(boolean verbose);
- private native void destroyInstance0(long moviePtr);
-
- private native void setStream0(long moviePtr, String url, int vid, int aid);
-
- private native int getVideoPTS0(long moviePtr);
-
- private native int getAudioPTS0(long moviePtr);
- private native Buffer getAudioBuffer0(long moviePtr, int plane);
-
- private native int readNextPacket0(long moviePtr, long procAddrGLTexSubImage2D, int texTarget, int texFmt, int texType);
-
- private native int seek0(long moviePtr, int position);
-
- public static enum PixelFormat {
- // NONE= -1,
- YUV420P, ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
- YUYV422, ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr
- RGB24, ///< packed RGB 8:8:8, 24bpp, RGBRGB...
- BGR24, ///< packed RGB 8:8:8, 24bpp, BGRBGR...
- YUV422P, ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)
- YUV444P, ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples)
- YUV410P, ///< planar YUV 4:1:0, 9bpp, (1 Cr & Cb sample per 4x4 Y samples)
- YUV411P, ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples)
- GRAY8, ///< Y , 8bpp
- MONOWHITE, ///< Y , 1bpp, 0 is white, 1 is black, in each byte pixels are ordered from the msb to the lsb
- MONOBLACK, ///< Y , 1bpp, 0 is black, 1 is white, in each byte pixels are ordered from the msb to the lsb
- PAL8, ///< 8 bit with RGB32 palette
- YUVJ420P, ///< planar YUV 4:2:0, 12bpp, full scale (JPEG), deprecated in favor of YUV420P and setting color_range
- YUVJ422P, ///< planar YUV 4:2:2, 16bpp, full scale (JPEG), deprecated in favor of YUV422P and setting color_range
- YUVJ444P, ///< planar YUV 4:4:4, 24bpp, full scale (JPEG), deprecated in favor of YUV444P and setting color_range
- XVMC_MPEG2_MC,///< XVideo Motion Acceleration via common packet passing
- XVMC_MPEG2_IDCT,
- UYVY422, ///< packed YUV 4:2:2, 16bpp, Cb Y0 Cr Y1
- UYYVYY411, ///< packed YUV 4:1:1, 12bpp, Cb Y0 Y1 Cr Y2 Y3
- BGR8, ///< packed RGB 3:3:2, 8bpp, (msb)2B 3G 3R(lsb)
- BGR4, ///< packed RGB 1:2:1 bitstream, 4bpp, (msb)1B 2G 1R(lsb), a byte contains two pixels, the first pixel in the byte is the one composed by the 4 msb bits
- BGR4_BYTE, ///< packed RGB 1:2:1, 8bpp, (msb)1B 2G 1R(lsb)
- RGB8, ///< packed RGB 3:3:2, 8bpp, (msb)2R 3G 3B(lsb)
- RGB4, ///< packed RGB 1:2:1 bitstream, 4bpp, (msb)1R 2G 1B(lsb), a byte contains two pixels, the first pixel in the byte is the one composed by the 4 msb bits
- RGB4_BYTE, ///< packed RGB 1:2:1, 8bpp, (msb)1R 2G 1B(lsb)
- NV12, ///< planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V)
- NV21, ///< as above, but U and V bytes are swapped
-
- ARGB, ///< packed ARGB 8:8:8:8, 32bpp, ARGBARGB...
- RGBA, ///< packed RGBA 8:8:8:8, 32bpp, RGBARGBA...
- ABGR, ///< packed ABGR 8:8:8:8, 32bpp, ABGRABGR...
- BGRA, ///< packed BGRA 8:8:8:8, 32bpp, BGRABGRA...
-
- GRAY16BE, ///< Y , 16bpp, big-endian
- GRAY16LE, ///< Y , 16bpp, little-endian
- YUV440P, ///< planar YUV 4:4:0 (1 Cr & Cb sample per 1x2 Y samples)
- YUVJ440P, ///< planar YUV 4:4:0 full scale (JPEG), deprecated in favor of YUV440P and setting color_range
- YUVA420P, ///< planar YUV 4:2:0, 20bpp, (1 Cr & Cb sample per 2x2 Y & A samples)
- VDPAU_H264,///< H.264 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers
- VDPAU_MPEG1,///< MPEG-1 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers
- VDPAU_MPEG2,///< MPEG-2 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers
- VDPAU_WMV3,///< WMV3 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers
- VDPAU_VC1, ///< VC-1 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers
- RGB48BE, ///< packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the 2-byte value for each R/G/B component is stored as big-endian
- RGB48LE, ///< packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the 2-byte value for each R/G/B component is stored as little-endian
-
- RGB565BE, ///< packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), big-endian
- RGB565LE, ///< packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), little-endian
- RGB555BE, ///< packed RGB 5:5:5, 16bpp, (msb)1A 5R 5G 5B(lsb), big-endian, most significant bit to 0
- RGB555LE, ///< packed RGB 5:5:5, 16bpp, (msb)1A 5R 5G 5B(lsb), little-endian, most significant bit to 0
-
- BGR565BE, ///< packed BGR 5:6:5, 16bpp, (msb) 5B 6G 5R(lsb), big-endian
- BGR565LE, ///< packed BGR 5:6:5, 16bpp, (msb) 5B 6G 5R(lsb), little-endian
- BGR555BE, ///< packed BGR 5:5:5, 16bpp, (msb)1A 5B 5G 5R(lsb), big-endian, most significant bit to 1
- BGR555LE, ///< packed BGR 5:5:5, 16bpp, (msb)1A 5B 5G 5R(lsb), little-endian, most significant bit to 1
-
- VAAPI_MOCO, ///< HW acceleration through VA API at motion compensation entry-point, Picture.data[3] contains a vaapi_render_state struct which contains macroblocks as well as various fields extracted from headers
- VAAPI_IDCT, ///< HW acceleration through VA API at IDCT entry-point, Picture.data[3] contains a vaapi_render_state struct which contains fields extracted from headers
- VAAPI_VLD, ///< HW decoding through VA API, Picture.data[3] contains a vaapi_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers
-
- YUV420P16LE, ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
- YUV420P16BE, ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
- YUV422P16LE, ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
- YUV422P16BE, ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
- YUV444P16LE, ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
- YUV444P16BE, ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
- VDPAU_MPEG4, ///< MPEG4 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers
- DXVA2_VLD, ///< HW decoding through DXVA2, Picture.data[3] contains a LPDIRECT3DSURFACE9 pointer
-
- RGB444LE, ///< packed RGB 4:4:4, 16bpp, (msb)4A 4R 4G 4B(lsb), little-endian, most significant bits to 0
- RGB444BE, ///< packed RGB 4:4:4, 16bpp, (msb)4A 4R 4G 4B(lsb), big-endian, most significant bits to 0
- BGR444LE, ///< packed BGR 4:4:4, 16bpp, (msb)4A 4B 4G 4R(lsb), little-endian, most significant bits to 1
- BGR444BE, ///< packed BGR 4:4:4, 16bpp, (msb)4A 4B 4G 4R(lsb), big-endian, most significant bits to 1
- Y400A, ///< 8bit gray, 8bit alpha
- BGR48BE, ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the 2-byte value for each R/G/B component is stored as big-endian
- BGR48LE, ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the 2-byte value for each R/G/B component is stored as little-endian
- YUV420P9BE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
- YUV420P9LE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
- YUV420P10BE,///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian
- YUV420P10LE,///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian
- YUV422P10BE,///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
- YUV422P10LE,///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
- YUV444P9BE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
- YUV444P9LE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
- YUV444P10BE,///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian
- YUV444P10LE,///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian
- YUV422P9BE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian
- YUV422P9LE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian
- VDA_VLD, ///< hardware decoding through VDA
- GBRP, ///< planar GBR 4:4:4 24bpp
- GBRP9BE, ///< planar GBR 4:4:4 27bpp, big endian
- GBRP9LE, ///< planar GBR 4:4:4 27bpp, little endian
- GBRP10BE, ///< planar GBR 4:4:4 30bpp, big endian
- GBRP10LE, ///< planar GBR 4:4:4 30bpp, little endian
- GBRP16BE, ///< planar GBR 4:4:4 48bpp, big endian
- GBRP16LE, ///< planar GBR 4:4:4 48bpp, little endian
- COUNT ///< number of pixel formats in this list
- ;
- public static PixelFormat valueOf(int i) {
- for (PixelFormat fmt : PixelFormat.values()) {
- if(fmt.ordinal() == i) {
- return fmt;
- }
- }
- return null;
+
+ final void pushSound(final ByteBuffer sampleData, final int data_size, final int audio_pts) {
+ setFirstAudioPTS2SCR( audio_pts );
+ if( 1.0f == getPlaySpeed() || audioSinkPlaySpeedSet ) {
+ audioSink.enqueueData( audio_pts, sampleData, data_size);
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGNatives.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGNatives.java
new file mode 100644
index 000000000..8fd439082
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGNatives.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.av.impl;
+
+import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame;
+
+/* pp */ abstract class FFMPEGNatives {
+
+ private static final Object mutex_avcodec_openclose_jni = new Object();
+
+ final boolean initSymbols0(final long[] symbols, final int count) {
+ return initSymbols0(mutex_avcodec_openclose_jni, symbols, count);
+ }
+ abstract boolean initSymbols0(Object mutex_avcodec_openclose, long[] symbols, int count);
+ abstract int getAvUtilMajorVersionCC0();
+ abstract int getAvFormatMajorVersionCC0();
+ abstract int getAvCodecMajorVersionCC0();
+ abstract int getAvResampleMajorVersionCC0();
+ abstract int getSwResampleMajorVersionCC0();
+
+ abstract long createInstance0(FFMPEGMediaPlayer upstream, boolean verbose);
+ abstract void destroyInstance0(long moviePtr);
+
+ /**
+ * Issues {@link #updateAttributes(int, int, int, int, int, int, int, float, int, int, String, String)}
+ * and {@link #updateAttributes2(int, int, int, int, int, int, int, int, int, int)}.
+ *
+ * @param moviePtr
+ * @param url
+ * @param vid
+ * @param sizes requested video size as string, i.e. 'hd720'. May be null to favor vWidth and vHeight.
+ * @param vWidth requested video width (for camera mode)
+ * @param vHeight requested video width (for camera mode)
+ * @param vRate requested video framerate (for camera mode)
+ * @param aid
+ * @param aPrefSampleRate
+ * @param aPrefChannelCount
+ */
+ abstract void setStream0(long moviePtr, String url, boolean isCameraInput,
+ int vid, String sizes, int vWidth, int vHeight,
+ int vRate, int aid, int aMaxChannelCount, int aPrefSampleRate);
+
+ abstract void setGLFuncs0(long moviePtr, long procAddrGLTexSubImage2D, long procAddrGLGetError, long procAddrGLFlush, long procAddrGLFinish);
+
+ abstract int getVideoPTS0(long moviePtr);
+
+ abstract int getAudioPTS0(long moviePtr);
+
+ /**
+ * @return resulting current video PTS, or {@link TextureFrame#INVALID_PTS}
+ */
+ abstract int readNextPacket0(long moviePtr, int texTarget, int texFmt, int texType);
+
+ abstract int play0(long moviePtr);
+ abstract int pause0(long moviePtr);
+ abstract int seek0(long moviePtr, int position);
+} \ No newline at end of file
diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGStaticNatives.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGStaticNatives.java
new file mode 100644
index 000000000..65a7e3618
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGStaticNatives.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.av.impl;
+
+import com.jogamp.common.util.VersionNumber;
+
+class FFMPEGStaticNatives {
+ static VersionNumber getAVVersion(final int vers) {
+ return new VersionNumber( ( vers >> 16 ) & 0xFF,
+ ( vers >> 8 ) & 0xFF,
+ ( vers >> 0 ) & 0xFF );
+ }
+ static native boolean initIDs0();
+
+ static native int getAvVersion0(long func);
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv08Natives.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv08Natives.java
new file mode 100644
index 000000000..6bab23f25
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv08Natives.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.av.impl;
+
+class FFMPEGv08Natives extends FFMPEGNatives {
+ @Override
+ native boolean initSymbols0(Object mutex_avcodec_openclose, long[] symbols, int count);
+
+ @Override
+ native int getAvUtilMajorVersionCC0();
+
+ @Override
+ native int getAvFormatMajorVersionCC0();
+
+ @Override
+ native int getAvCodecMajorVersionCC0();
+
+ @Override
+ native int getAvResampleMajorVersionCC0();
+
+ @Override
+ native int getSwResampleMajorVersionCC0();
+
+ @Override
+ native long createInstance0(FFMPEGMediaPlayer upstream, boolean verbose);
+
+ @Override
+ native void destroyInstance0(long moviePtr);
+
+ @Override
+ native void setStream0(long moviePtr, String url, boolean isCameraInput, int vid, String sizes, int vWidth, int vHeight, int vRate, int aid, int aMaxChannelCount, int aPrefSampleRate);
+
+ @Override
+ native void setGLFuncs0(long moviePtr, long procAddrGLTexSubImage2D, long procAddrGLGetError, long procAddrGLFlush, long procAddrGLFinish);
+
+ @Override
+ native int getVideoPTS0(long moviePtr);
+
+ @Override
+ native int getAudioPTS0(long moviePtr);
+
+ @Override
+ native int readNextPacket0(long moviePtr, int texTarget, int texFmt, int texType);
+
+ @Override
+ native int play0(long moviePtr);
+
+ @Override
+ native int pause0(long moviePtr);
+
+ @Override
+ native int seek0(long moviePtr, int position);
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv09Natives.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv09Natives.java
new file mode 100644
index 000000000..a48b5f21f
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv09Natives.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.av.impl;
+
+class FFMPEGv09Natives extends FFMPEGNatives {
+ @Override
+ native boolean initSymbols0(Object mutex_avcodec_openclose, long[] symbols, int count);
+
+ @Override
+ native int getAvUtilMajorVersionCC0();
+
+ @Override
+ native int getAvFormatMajorVersionCC0();
+
+ @Override
+ native int getAvCodecMajorVersionCC0();
+
+ @Override
+ native int getAvResampleMajorVersionCC0();
+
+ @Override
+ native int getSwResampleMajorVersionCC0();
+
+ @Override
+ native long createInstance0(FFMPEGMediaPlayer upstream, boolean verbose);
+
+ @Override
+ native void destroyInstance0(long moviePtr);
+
+ @Override
+ native void setStream0(long moviePtr, String url, boolean isCameraInput, int vid, String sizes, int vWidth, int vHeight, int vRate, int aid, int aMaxChannelCount, int aPrefSampleRate);
+
+ @Override
+ native void setGLFuncs0(long moviePtr, long procAddrGLTexSubImage2D, long procAddrGLGetError, long procAddrGLFlush, long procAddrGLFinish);
+
+ @Override
+ native int getVideoPTS0(long moviePtr);
+
+ @Override
+ native int getAudioPTS0(long moviePtr);
+
+ @Override
+ native int readNextPacket0(long moviePtr, int texTarget, int texFmt, int texType);
+
+ @Override
+ native int play0(long moviePtr);
+
+ @Override
+ native int pause0(long moviePtr);
+
+ @Override
+ native int seek0(long moviePtr, int position);
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv10Natives.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv10Natives.java
new file mode 100644
index 000000000..f35fb29dc
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGv10Natives.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.av.impl;
+
+class FFMPEGv10Natives extends FFMPEGNatives {
+ @Override
+ native boolean initSymbols0(Object mutex_avcodec_openclose, long[] symbols, int count);
+
+ @Override
+ native int getAvUtilMajorVersionCC0();
+
+ @Override
+ native int getAvFormatMajorVersionCC0();
+
+ @Override
+ native int getAvCodecMajorVersionCC0();
+
+ @Override
+ native int getAvResampleMajorVersionCC0();
+
+ @Override
+ native int getSwResampleMajorVersionCC0();
+
+ @Override
+ native long createInstance0(FFMPEGMediaPlayer upstream, boolean verbose);
+
+ @Override
+ native void destroyInstance0(long moviePtr);
+
+ @Override
+ native void setStream0(long moviePtr, String url, boolean isCameraInput, int vid, String sizes, int vWidth, int vHeight, int vRate, int aid, int aMaxChannelCount, int aPrefSampleRate);
+
+ @Override
+ native void setGLFuncs0(long moviePtr, long procAddrGLTexSubImage2D, long procAddrGLGetError, long procAddrGLFlush, long procAddrGLFinish);
+
+ @Override
+ native int getVideoPTS0(long moviePtr);
+
+ @Override
+ native int getAudioPTS0(long moviePtr);
+
+ @Override
+ native int readNextPacket0(long moviePtr, int texTarget, int texFmt, int texType);
+
+ @Override
+ native int play0(long moviePtr);
+
+ @Override
+ native int pause0(long moviePtr);
+
+ @Override
+ native int seek0(long moviePtr, int position);
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java
index aef98fcde..5baf9e543 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java
@@ -3,14 +3,14 @@
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
- *
+ *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
@@ -20,7 +20,7 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
@@ -29,11 +29,9 @@
package jogamp.opengl.util.av.impl;
import java.io.IOException;
-import java.net.URL;
import javax.media.opengl.GL;
import javax.media.opengl.GLException;
-import javax.media.opengl.GLProfile;
import com.jogamp.opengl.util.texture.TextureSequence;
@@ -47,18 +45,18 @@ import jogamp.opengl.util.av.EGLMediaPlayerImpl;
*/
public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
static final boolean available;
-
+
static {
- // OMX binding is included in jogl_desktop and jogl_mobile
+ available = false;
+ /** FIXME!
+ // OMX binding is included in jogl_desktop and jogl_mobile
GLProfile.initSingleton();
- available = initIDs0();
+ available = initIDs0(); */
}
-
+
public static final boolean isAvailable() { return available; }
-
+
protected long moviePtr = 0;
-
- protected TextureSequence.TextureFrame lastTex = null;
public OMXGLMediaPlayer() {
super(TextureType.KHRImage, true);
@@ -72,56 +70,65 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
moviePtr = _createInstance();
if(0==moviePtr) {
throw new GLException("Couldn't create OMXInstance");
- }
+ }
}
-
+
@Override
- protected TextureSequence.TextureFrame createTexImage(GL gl, int idx, int[] tex) {
- final EGLTextureFrame eglTex = (EGLTextureFrame) super.createTexImage(gl, idx, tex);
- _setStreamEGLImageTexture2D(moviePtr, idx, tex[idx], eglTex.getImage(), eglTex.getSync());
- lastTex = eglTex;
+ protected TextureSequence.TextureFrame createTexImage(final GL gl, final int texName) {
+ final EGLTextureFrame eglTex = (EGLTextureFrame) super.createTexImage(gl, texName);
+ _setStreamEGLImageTexture2D(moviePtr, texName, eglTex.getImage(), eglTex.getSync());
return eglTex;
}
-
+
@Override
- protected void destroyTexImage(GL gl, TextureSequence.TextureFrame imgTex) {
- lastTex = null;
- super.destroyTexImage(gl, imgTex);
+ protected void destroyTexFrame(final GL gl, final TextureSequence.TextureFrame imgTex) {
+ super.destroyTexFrame(gl, imgTex);
}
-
+
@Override
- protected void destroyImpl(GL gl) {
- _detachVideoRenderer(moviePtr);
+ protected void destroyImpl(final GL gl) {
if (moviePtr != 0) {
+ _stop(moviePtr);
+ _detachVideoRenderer(moviePtr);
_destroyInstance(moviePtr);
moviePtr = 0;
}
}
-
+
@Override
- protected void initGLStreamImpl(GL gl, int[] texNames) throws IOException {
+ protected void initStreamImpl(final int vid, final int aid) throws IOException {
if(0==moviePtr) {
throw new GLException("OMX native instance null");
}
- final URL url = urlConn.getURL();
- if(!url.getProtocol().equals("file")) {
- throw new IOException("Only file URLs are allowed: "+url);
+ if( !getUri().isFileScheme() ) {
+ throw new IOException("Only file schemes are allowed: "+getUri());
+ }
+ final String path=getUri().path.decode();
+ if(DEBUG) {
+ System.out.println("initGLStream: clean path "+path);
+ }
+
+ if(DEBUG) {
+ System.out.println("initGLStream: p1 "+this);
+ }
+ _setStream(moviePtr, getTextureCount(), path);
+ if(DEBUG) {
+ System.out.println("initGLStream: p2 "+this);
}
- final String path=url.getPath();
- System.out.println("setURL: clean path "+path);
-
- System.out.println("setURL: p1 "+this);
- _setStream(moviePtr, textureCount, path);
- System.out.println("setURL: p2 "+this);
}
-
@Override
- protected int getCurrentPositionImpl() {
+ protected final void initGLImpl(final GL gl) throws IOException, GLException {
+ // NOP
+ setIsGLOriented(true);
+ }
+
+ @Override
+ protected int getAudioPTSImpl() {
return 0!=moviePtr ? _getCurrentPosition(moviePtr) : 0;
}
@Override
- protected boolean setPlaySpeedImpl(float rate) {
+ protected boolean setPlaySpeedImpl(final float rate) {
if(0==moviePtr) {
throw new GLException("OMX native instance null");
}
@@ -130,7 +137,7 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
}
@Override
- public synchronized boolean startImpl() {
+ public synchronized boolean playImpl() {
if(0==moviePtr) {
return false;
}
@@ -150,17 +157,7 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
/** @return time position after issuing the command */
@Override
- public synchronized boolean stopImpl() {
- if(0==moviePtr) {
- return false;
- }
- _stop(moviePtr);
- return true;
- }
-
- /** @return time position after issuing the command */
- @Override
- protected int seekImpl(int msec) {
+ protected int seekImpl(final int msec) {
if(0==moviePtr) {
throw new GLException("OMX native instance null");
}
@@ -168,39 +165,36 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
}
@Override
- protected TextureSequence.TextureFrame getLastTextureImpl() {
- return lastTex;
- }
-
- @Override
- protected TextureSequence.TextureFrame getNextTextureImpl(GL gl, boolean blocking) {
+ protected int getNextTextureImpl(final GL gl, final TextureFrame nextFrame) {
if(0==moviePtr) {
throw new GLException("OMX native instance null");
}
- final int nextTex = _getNextTextureID(moviePtr, blocking);
+ final int nextTex = _getNextTextureID(moviePtr, true);
if(0 < nextTex) {
- final TextureSequence.TextureFrame eglImgTex = texFrameMap.get(new Integer(_getNextTextureID(moviePtr, blocking)));
+ // FIXME set pts !
+ /* FIXME
+ final TextureSequence.TextureFrame eglImgTex =
+ texFrameMap.get(new Integer(_getNextTextureID(moviePtr, blocking)));
if(null!=eglImgTex) {
lastTex = eglImgTex;
- }
+ } */
}
- return lastTex;
+ return 0; // FIXME: return pts
}
-
- private String replaceAll(String orig, String search, String repl) {
- String dest=null;
+
+ private String replaceAll(final String orig, final String search, final String repl) {
+ final StringBuilder dest = new StringBuilder();
// In case replaceAll / java.util.regex.* is not supported (-> CVM)
int i=0,j;
- dest = new String();
while((j=orig.indexOf(search, i))>=0) {
- dest=dest.concat(orig.substring(i, j));
- dest=dest.concat(repl);
+ dest.append(orig.substring(i, j));
+ dest.append(repl);
i=j+1;
}
- return dest.concat(orig.substring(i, orig.length()));
+ return dest.append(orig.substring(i, orig.length())).toString();
}
- private void errorCheckEGL(String s) {
+ private void errorCheckEGL(final String s) {
int e;
if( (e=EGL.eglGetError()) != EGL.EGL_SUCCESS ) {
System.out.println("EGL Error: ("+s+"): 0x"+Integer.toHexString(e));
@@ -208,15 +202,15 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
}
private static native boolean initIDs0();
- private native long _createInstance();
+ private native long _createInstance();
private native void _destroyInstance(long moviePtr);
-
+
private native void _detachVideoRenderer(long moviePtr); // stop before
private native void _attachVideoRenderer(long moviePtr); // detach before
private native void _setStream(long moviePtr, int textureNum, String path);
private native void _activateStream(long moviePtr);
-
- private native void _setStreamEGLImageTexture2D(long moviePtr, int i, int tex, long image, long sync);
+
+ private native void _setStreamEGLImageTexture2D(long moviePtr, int tex, long image, long sync);
private native int _seek(long moviePtr, int position);
private native void _setPlaySpeed(long moviePtr, float rate);
private native void _play(long moviePtr);
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandler.java b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandler.java
index 602d283d6..5a78f514d 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandler.java
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandler.java
@@ -33,40 +33,49 @@ import java.nio.Buffer;
import javax.media.opengl.GL;
import javax.media.opengl.GL2ES2;
-import jogamp.opengl.util.GLArrayHandler;
import jogamp.opengl.util.GLArrayHandlerFlat;
+import jogamp.opengl.util.GLVBOArrayHandler;
import com.jogamp.opengl.util.GLArrayDataEditable;
import com.jogamp.opengl.util.glsl.ShaderState;
/**
- * Used for 1:1 GLSL arrays, i.e. where the buffer data
- * represents this array only.
+ * Used for 1:1 GLSL arrays, i.e. where the buffer data
+ * represents this array only.
*/
-public class GLSLArrayHandler implements GLArrayHandler {
- private GLArrayDataEditable ad;
+public class GLSLArrayHandler extends GLVBOArrayHandler {
- public GLSLArrayHandler(GLArrayDataEditable ad) {
- this.ad = ad;
+ public GLSLArrayHandler(final GLArrayDataEditable ad) {
+ super(ad);
}
-
- public final void setSubArrayVBOName(int vboName) {
+
+ @Override
+ public final void setSubArrayVBOName(final int vboName) {
throw new UnsupportedOperationException();
}
-
- public final void addSubHandler(GLArrayHandlerFlat handler) {
+
+ @Override
+ public final void addSubHandler(final GLArrayHandlerFlat handler) {
throw new UnsupportedOperationException();
}
-
- public final void syncData(GL gl, boolean enable, Object ext) {
+
+ @Override
+ public final void enableState(final GL gl, final boolean enable, final Object ext) {
final GL2ES2 glsl = gl.getGL2ES2();
- final ShaderState st = (ShaderState) ext;
-
+ if( null != ext ) {
+ enableShaderState(glsl, enable, (ShaderState)ext);
+ } else {
+ enableSimple(glsl, enable);
+ }
+ }
+
+ private final int[] tempI = new int[1];
+
+ private final void enableShaderState(final GL2ES2 glsl, final boolean enable, final ShaderState st) {
if(enable) {
- final Buffer buffer = ad.getBuffer();
/*
* This would be the non optimized code path:
- *
+ *
if(ad.isVBO()) {
glsl.glBindBuffer(ad.getVBOTarget(), ad.getVBOName());
if(!ad.isVBOWritten()) {
@@ -78,6 +87,7 @@ public class GLSLArrayHandler implements GLArrayHandler {
}
st.vertexAttribPointer(glsl, ad);
*/
+ final Buffer buffer = ad.getBuffer();
if(ad.isVBO()) {
// bind and refresh the VBO / vertex-attr only if necessary
if(!ad.isVBOWritten()) {
@@ -87,34 +97,76 @@ public class GLSLArrayHandler implements GLArrayHandler {
}
ad.setVBOWritten(true);
st.vertexAttribPointer(glsl, ad);
+ glsl.glBindBuffer(ad.getVBOTarget(), 0);
} else if(st.getAttribLocation(glsl, ad) >= 0) {
// didn't experience a performance hit on this query ..
// (using ShaderState's location query above to validate the location)
- final int[] qi = new int[1];
- glsl.glGetVertexAttribiv(ad.getLocation(), GL2ES2.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, qi, 0);
- if(ad.getVBOName() != qi[0]) {
+ glsl.glGetVertexAttribiv(ad.getLocation(), GL2ES2.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, tempI, 0);
+ if(ad.getVBOName() != tempI[0]) {
glsl.glBindBuffer(ad.getVBOTarget(), ad.getVBOName());
st.vertexAttribPointer(glsl, ad);
+ glsl.glBindBuffer(ad.getVBOTarget(), 0);
}
}
} else if(null!=buffer) {
st.vertexAttribPointer(glsl, ad);
}
- } else if(ad.isVBO()) {
- glsl.glBindBuffer(ad.getVBOTarget(), 0);
- }
- }
-
- public final void enableState(GL gl, boolean enable, Object ext) {
- final GL2ES2 glsl = gl.getGL2ES2();
- final ShaderState st = (ShaderState) ext;
-
- if(enable) {
+
st.enableVertexAttribArray(glsl, ad);
} else {
st.disableVertexAttribArray(glsl, ad);
}
}
+ private final void enableSimple(final GL2ES2 glsl, final boolean enable) {
+ final int location = ad.getLocation();
+ if( 0 > location ) {
+ return;
+ }
+ if(enable) {
+ /*
+ * This would be the non optimized code path:
+ *
+ if(ad.isVBO()) {
+ glsl.glBindBuffer(ad.getVBOTarget(), ad.getVBOName());
+ if(!ad.isVBOWritten()) {
+ if(null!=buffer) {
+ glsl.glBufferData(ad.getVBOTarget(), ad.getSizeInBytes(), buffer, ad.getVBOUsage());
+ }
+ ad.setVBOWritten(true);
+ }
+ }
+ st.vertexAttribPointer(glsl, ad);
+ */
+ final Buffer buffer = ad.getBuffer();
+ if(ad.isVBO()) {
+ // bind and refresh the VBO / vertex-attr only if necessary
+ if(!ad.isVBOWritten()) {
+ glsl.glBindBuffer(ad.getVBOTarget(), ad.getVBOName());
+ if(null!=buffer) {
+ glsl.glBufferData(ad.getVBOTarget(), ad.getSizeInBytes(), buffer, ad.getVBOUsage());
+ }
+ ad.setVBOWritten(true);
+ glsl.glVertexAttribPointer(ad);
+ glsl.glBindBuffer(ad.getVBOTarget(), 0);
+ } else {
+ // didn't experience a performance hit on this query ..
+ // (using ShaderState's location query above to validate the location)
+ glsl.glGetVertexAttribiv(location, GL2ES2.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, tempI, 0);
+ if(ad.getVBOName() != tempI[0]) {
+ glsl.glBindBuffer(ad.getVBOTarget(), ad.getVBOName());
+ glsl.glVertexAttribPointer(ad);
+ glsl.glBindBuffer(ad.getVBOTarget(), 0);
+ }
+ }
+ } else if(null!=buffer) {
+ glsl.glVertexAttribPointer(ad);
+ }
+
+ glsl.glEnableVertexAttribArray(location);
+ } else {
+ glsl.glDisableVertexAttribArray(location);
+ }
+ }
}
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerFlat.java b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerFlat.java
index c4b761b13..85fcabdd9 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerFlat.java
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerFlat.java
@@ -37,53 +37,67 @@ import com.jogamp.opengl.util.GLArrayDataWrapper;
import com.jogamp.opengl.util.glsl.ShaderState;
/**
- * Used for interleaved GLSL arrays, i.e. where the buffer data itself is handled
+ * Used for interleaved GLSL arrays, i.e. where the buffer data itself is handled
* separately and interleaves many arrays.
*/
public class GLSLArrayHandlerFlat implements GLArrayHandlerFlat {
- private GLArrayDataWrapper ad;
+ private final GLArrayDataWrapper ad;
- public GLSLArrayHandlerFlat(GLArrayDataWrapper ad) {
+ public GLSLArrayHandlerFlat(final GLArrayDataWrapper ad) {
this.ad = ad;
}
+ @Override
public GLArrayDataWrapper getData() {
return ad;
}
-
- public final void syncData(GL gl, boolean enable, boolean force, Object ext) {
- if(enable) {
- final GL2ES2 glsl = gl.getGL2ES2();
- final ShaderState st = (ShaderState) ext;
+ @Override
+ public final void syncData(final GL gl, final Object ext) {
+ final GL2ES2 glsl = gl.getGL2ES2();
+ if( null != ext ) {
+ ((ShaderState)ext).vertexAttribPointer(glsl, ad);
+ } else {
+ if( 0 <= ad.getLocation() ) {
+ glsl.glVertexAttribPointer(ad);
+ }
+ }
+ /**
+ * Due to probable application VBO switching, this might not make any sense ..
+ *
+ if(!written) {
st.vertexAttribPointer(glsl, ad);
- /**
- * Due to probable application VBO switching, this might not make any sense ..
- *
- if(force) {
+ } else if(st.getAttribLocation(glsl, ad) >= 0) {
+ final int[] qi = new int[1];
+ glsl.glGetVertexAttribiv(ad.getLocation(), GL2ES2.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, qi, 0);
+ if(ad.getVBOName() != qi[0]) {
+ System.err.println("XXX1: "+ad.getName()+", vbo ad "+ad.getVBOName()+", gl "+qi[0]+", "+ad);
st.vertexAttribPointer(glsl, ad);
- } else if(st.getAttribLocation(glsl, ad) >= 0) {
- final int[] qi = new int[1];
- glsl.glGetVertexAttribiv(ad.getLocation(), GL2ES2.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, qi, 0);
- if(ad.getVBOName() != qi[0]) {
- System.err.println("XXX1: "+ad.getName()+", vbo ad "+ad.getVBOName()+", gl "+qi[0]+", "+ad);
- st.vertexAttribPointer(glsl, ad);
- } else {
- System.err.println("XXX0: "+ad.getName()+", vbo ad "+ad.getVBOName()+", gl "+qi[0]+", "+ad);
- }
- }*/
- }
+ } else {
+ System.err.println("XXX0: "+ad.getName()+", vbo ad "+ad.getVBOName()+", gl "+qi[0]+", "+ad);
+ }
+ }*/
}
- public final void enableState(GL gl, boolean enable, Object ext) {
+ @Override
+ public final void enableState(final GL gl, final boolean enable, final Object ext) {
final GL2ES2 glsl = gl.getGL2ES2();
- final ShaderState st = (ShaderState) ext;
-
- if(enable) {
- st.enableVertexAttribArray(glsl, ad);
+ if( null != ext ) {
+ final ShaderState st = (ShaderState)ext;
+ if(enable) {
+ st.enableVertexAttribArray(glsl, ad);
+ } else {
+ st.disableVertexAttribArray(glsl, ad);
+ }
} else {
- st.disableVertexAttribArray(glsl, ad);
+ final int location = ad.getLocation();
+ if( 0 <= location ) {
+ if(enable) {
+ glsl.glEnableVertexAttribArray(location);
+ } else {
+ glsl.glDisableVertexAttribArray(location);
+ }
+ }
}
- }
+ }
}
-
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerInterleaved.java b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerInterleaved.java
index f50429623..0169b0747 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerInterleaved.java
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerInterleaved.java
@@ -28,75 +28,59 @@
package jogamp.opengl.util.glsl;
-import java.nio.Buffer;
import java.util.ArrayList;
import java.util.List;
import javax.media.opengl.GL;
-import jogamp.opengl.util.GLArrayHandler;
import jogamp.opengl.util.GLArrayHandlerFlat;
+import jogamp.opengl.util.GLVBOArrayHandler;
import com.jogamp.opengl.util.GLArrayDataEditable;
/**
- * Interleaved fixed function arrays, i.e. where this buffer data
- * represents many arrays.
+ * Interleaved fixed function arrays, i.e. where this buffer data
+ * represents many arrays.
*/
-public class GLSLArrayHandlerInterleaved implements GLArrayHandler {
- private GLArrayDataEditable ad;
- private List<GLArrayHandlerFlat> subArrays = new ArrayList<GLArrayHandlerFlat>();
+public class GLSLArrayHandlerInterleaved extends GLVBOArrayHandler {
+ private final List<GLArrayHandlerFlat> subArrays = new ArrayList<GLArrayHandlerFlat>();
- public GLSLArrayHandlerInterleaved(GLArrayDataEditable ad) {
- this.ad = ad;
+ public GLSLArrayHandlerInterleaved(final GLArrayDataEditable ad) {
+ super(ad);
}
-
- public final void setSubArrayVBOName(int vboName) {
+
+ @Override
+ public final void setSubArrayVBOName(final int vboName) {
for(int i=0; i<subArrays.size(); i++) {
subArrays.get(i).getData().setVBOName(vboName);
- }
+ }
}
-
- public final void addSubHandler(GLArrayHandlerFlat handler) {
+
+ @Override
+ public final void addSubHandler(final GLArrayHandlerFlat handler) {
subArrays.add(handler);
}
- private final void syncSubData(GL gl, boolean enable, boolean force, Object ext) {
+ private final void syncSubData(final GL gl, final Object ext) {
for(int i=0; i<subArrays.size(); i++) {
- subArrays.get(i).syncData(gl, enable, force, ext);
- }
- }
-
- public final void syncData(GL gl, boolean enable, Object ext) {
- if(!ad.isVBO()) {
- throw new InternalError("Interleaved handle is not VBO: "+ad);
- }
-
+ subArrays.get(i).syncData(gl, ext);
+ }
+ }
+
+ @Override
+ public final void enableState(final GL gl, final boolean enable, final Object ext) {
if(enable) {
- final Buffer buffer = ad.getBuffer();
- final boolean vboWritten = ad.isVBOWritten();
-
- // always bind and refresh the VBO mgr,
- // in case more than one gl*Pointer objects are in use
- gl.glBindBuffer(ad.getVBOTarget(), ad.getVBOName());
- if(!vboWritten) {
- if(null!=buffer) {
- gl.glBufferData(ad.getVBOTarget(), buffer.limit() * ad.getComponentSizeInBytes(), buffer, ad.getVBOUsage());
- }
- ad.setVBOWritten(true);
+ if(!ad.isVBO()) {
+ throw new InternalError("Interleaved handle is not VBO: "+ad);
}
- // sub data will decide weather to update the vertex attrib pointer
- syncSubData(gl, true, !vboWritten, ext);
- } else {
- // NOP on GLSL: syncSubData(gl, false, ext);
- gl.glBindBuffer(ad.getVBOTarget(), 0);
+ bindBuffer(gl, true);
+ // sub data will decide whether to update the vertex attrib pointer
+ syncSubData(gl, ext);
+ bindBuffer(gl, false);
}
- }
-
- public final void enableState(GL gl, boolean enable, Object ext) {
for(int i=0; i<subArrays.size(); i++) {
subArrays.get(i).enableState(gl, enable, ext);
- }
+ }
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/GLSLTextureRaster.java b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLTextureRaster.java
new file mode 100644
index 000000000..d5d0020c5
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLTextureRaster.java
@@ -0,0 +1,192 @@
+/**
+ * Copyright 2012 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+
+package jogamp.opengl.util.glsl;
+
+import java.nio.FloatBuffer;
+
+import com.jogamp.opengl.util.GLArrayDataServer;
+import com.jogamp.opengl.util.PMVMatrix;
+import com.jogamp.opengl.util.glsl.ShaderCode;
+import com.jogamp.opengl.util.glsl.ShaderProgram;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2ES2;
+import javax.media.opengl.GLArrayData;
+import javax.media.opengl.GLException;
+import javax.media.opengl.GLUniformData;
+import javax.media.opengl.fixedfunc.GLMatrixFunc;
+
+public class GLSLTextureRaster {
+ private final boolean textureVertFlipped;
+ private final int textureUnit;
+
+ private ShaderProgram sp;
+ private PMVMatrix pmvMatrix;
+ private GLUniformData pmvMatrixUniform;
+ private GLUniformData activeTexUniform;
+ private GLArrayDataServer interleavedVBO;
+
+ public GLSLTextureRaster(final int textureUnit, final boolean textureVertFlipped) {
+ this.textureVertFlipped = textureVertFlipped;
+ this.textureUnit = textureUnit;
+ }
+
+ public int getTextureUnit() { return textureUnit; }
+
+ static final String shaderBasename = "texture01_xxx";
+ static final String shaderSrcPath = "../../shader";
+ static final String shaderBinPath = "../../shader/bin";
+
+ public void init(final GL2ES2 gl) {
+ // Create & Compile the shader objects
+ final ShaderCode rsVp = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, this.getClass(),
+ shaderSrcPath, shaderBinPath, shaderBasename, true);
+ final ShaderCode rsFp = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, this.getClass(),
+ shaderSrcPath, shaderBinPath, shaderBasename, true);
+ rsVp.defaultShaderCustomization(gl, true, true);
+ rsFp.defaultShaderCustomization(gl, true, true);
+
+ // Create & Link the shader program
+ sp = new ShaderProgram();
+ sp.add(rsVp);
+ sp.add(rsFp);
+ if(!sp.link(gl, System.err)) {
+ throw new GLException("Couldn't link program: "+sp);
+ }
+ sp.useProgram(gl, true);
+
+ // setup mgl_PMVMatrix
+ pmvMatrix = new PMVMatrix();
+ pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
+ pmvMatrix.glLoadIdentity();
+ pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
+ pmvMatrix.glLoadIdentity();
+ pmvMatrixUniform = new GLUniformData("mgl_PMVMatrix", 4, 4, pmvMatrix.glGetPMvMatrixf()); // P, Mv
+ if( pmvMatrixUniform.setLocation(gl, sp.program()) < 0 ) {
+ throw new GLException("Couldn't locate "+pmvMatrixUniform+" in shader: "+sp);
+ }
+ gl.glUniform(pmvMatrixUniform);
+
+ activeTexUniform = new GLUniformData("mgl_Texture0", textureUnit);
+ if( activeTexUniform.setLocation(gl, sp.program()) < 0 ) {
+ throw new GLException("Couldn't locate "+activeTexUniform+" in shader: "+sp);
+ }
+ gl.glUniform(activeTexUniform);
+
+ final float[] s_quadTexCoords;
+ if( textureVertFlipped ) {
+ s_quadTexCoords = s_quadTexCoords01;
+ } else {
+ s_quadTexCoords = s_quadTexCoords00;
+ }
+
+ interleavedVBO = GLArrayDataServer.createGLSLInterleaved(3+2, GL.GL_FLOAT, false, 2*4, GL.GL_STATIC_DRAW);
+ {
+ final GLArrayData vArrayData = interleavedVBO.addGLSLSubArray("mgl_Vertex", 3, GL.GL_ARRAY_BUFFER);
+ if( vArrayData.setLocation(gl, sp.program()) < 0 ) {
+ throw new GLException("Couldn't locate "+vArrayData+" in shader: "+sp);
+ }
+ final GLArrayData tArrayData = interleavedVBO.addGLSLSubArray("mgl_MultiTexCoord", 2, GL.GL_ARRAY_BUFFER);
+ if( tArrayData.setLocation(gl, sp.program()) < 0 ) {
+ throw new GLException("Couldn't locate "+tArrayData+" in shader: "+sp);
+ }
+ final FloatBuffer ib = (FloatBuffer)interleavedVBO.getBuffer();
+ for(int i=0; i<4; i++) {
+ ib.put(s_quadVertices, i*3, 3);
+ ib.put(s_quadTexCoords, i*2, 2);
+ }
+ }
+ interleavedVBO.seal(gl, true);
+ interleavedVBO.enableBuffer(gl, false);
+
+ sp.useProgram(gl, false);
+ }
+
+ public void reshape(final GL2ES2 gl, final int x, final int y, final int width, final int height) {
+ if(null != sp) {
+ pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
+ pmvMatrix.glLoadIdentity();
+ pmvMatrix.glOrthof(-1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 10.0f);
+
+ pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
+ pmvMatrix.glLoadIdentity();
+
+ sp.useProgram(gl, true);
+ gl.glUniform(pmvMatrixUniform);
+ sp.useProgram(gl, false);
+ }
+ }
+
+ public void dispose(final GL2ES2 gl) {
+ if(null != pmvMatrixUniform) {
+ pmvMatrixUniform = null;
+ }
+ pmvMatrix=null;
+ if(null != interleavedVBO) {
+ interleavedVBO.destroy(gl);
+ interleavedVBO=null;
+ }
+ if(null != sp) {
+ sp.destroy(gl);
+ sp=null;
+ }
+ }
+
+ public void display(final GL2ES2 gl) {
+ if(null != sp) {
+ sp.useProgram(gl, true);
+ interleavedVBO.enableBuffer(gl, true);
+
+ gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, 4);
+
+ interleavedVBO.enableBuffer(gl, false);
+ sp.useProgram(gl, false);
+ }
+ }
+
+ private static final float[] s_quadVertices = {
+ -1f, -1f, 0f, // LB
+ 1f, -1f, 0f, // RB
+ -1f, 1f, 0f, // LT
+ 1f, 1f, 0f // RT
+ };
+ private static final float[] s_quadTexCoords00 = {
+ 0f, 0f, // LB
+ 1f, 0f, // RB
+ 0f, 1f, // LT
+ 1f, 1f // RT
+ };
+ private static final float[] s_quadTexCoords01 = {
+ 0f, 1f, // LB
+ 1f, 1f, // RB
+ 0f, 0f, // LT
+ 1f, 0f // RT
+ };
+}
+
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java
index 897967f8b..2dde27b1d 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java
@@ -29,109 +29,159 @@
package jogamp.opengl.util.glsl.fixedfunc;
-import javax.media.opengl.*;
-import javax.media.opengl.fixedfunc.*;
-import com.jogamp.common.nio.Buffers;
-import com.jogamp.opengl.util.*;
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2ES2;
+import javax.media.opengl.GLArrayData;
+import javax.media.opengl.GLException;
+import javax.media.opengl.GLProfile;
+import javax.media.opengl.fixedfunc.GLLightingFunc;
+import javax.media.opengl.fixedfunc.GLMatrixFunc;
+import javax.media.opengl.fixedfunc.GLPointerFunc;
-import java.nio.*;
+import com.jogamp.common.nio.Buffers;
+import com.jogamp.common.util.ValueConv;
+import com.jogamp.opengl.util.GLArrayDataWrapper;
+import com.jogamp.opengl.util.GLBuffers;
+import com.jogamp.opengl.util.PMVMatrix;
+import com.jogamp.opengl.util.glsl.fixedfunc.ShaderSelectionMode;
public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFunc {
public static final int MAX_TEXTURE_UNITS = 8;
- protected FixedFuncPipeline fixedFunction=null;
- protected PMVMatrix pmvMatrix=null;
- protected GL2ES2 gl=null;
+ protected final GLProfile gl2es1GLProfile;
+ protected FixedFuncPipeline fixedFunction;
+ protected PMVMatrix pmvMatrix;
+ protected boolean ownsPMVMatrix;
+ protected GL2ES2 gl;
- public FixedFuncHook (GL2ES2 gl) {
- this(gl, null);
+ /**
+ * @param gl
+ * @param mode TODO
+ * @param pmvMatrix optional pass through PMVMatrix for the {@link FixedFuncHook} and {@link FixedFuncPipeline}
+ */
+ public FixedFuncHook (final GL2ES2 gl, final ShaderSelectionMode mode, final PMVMatrix pmvMatrix) {
+ this.gl2es1GLProfile = GLProfile.createCustomGLProfile(GLProfile.GL2ES1, gl.getGLProfile().getImpl());
+ this.gl = gl;
+ if(null != pmvMatrix) {
+ this.ownsPMVMatrix = false;
+ this.pmvMatrix = pmvMatrix;
+ } else {
+ this.ownsPMVMatrix = true;
+ this.pmvMatrix = new PMVMatrix();
+ }
+ fixedFunction = new FixedFuncPipeline(this.gl, mode, this.pmvMatrix);
}
- public FixedFuncHook (GL2ES2 gl, PMVMatrix matrix) {
+ /**
+ * @param gl
+ * @param mode TODO
+ * @param pmvMatrix optional pass through PMVMatrix for the {@link FixedFuncHook} and {@link FixedFuncPipeline}
+ */
+ public FixedFuncHook(final GL2ES2 gl, final ShaderSelectionMode mode, final PMVMatrix pmvMatrix,
+ final Class<?> shaderRootClass, final String shaderSrcRoot, final String shaderBinRoot,
+ final String vertexColorFile, final String vertexColorLightFile,
+ final String fragmentColorFile, final String fragmentColorTextureFile) {
+ this.gl2es1GLProfile = GLProfile.createCustomGLProfile(GLProfile.GL2ES1, gl.getGLProfile().getImpl());
this.gl = gl;
- pmvMatrix = (null!=matrix)?matrix:new PMVMatrix();
+ if(null != pmvMatrix) {
+ this.ownsPMVMatrix = false;
+ this.pmvMatrix = pmvMatrix;
+ } else {
+ this.ownsPMVMatrix = true;
+ this.pmvMatrix = new PMVMatrix();
+ }
- fixedFunction = new FixedFuncPipeline(gl, pmvMatrix);
+ fixedFunction = new FixedFuncPipeline(this.gl, mode, this.pmvMatrix, shaderRootClass, shaderSrcRoot,
+ shaderBinRoot, vertexColorFile, vertexColorLightFile, fragmentColorFile, fragmentColorTextureFile);
}
- public FixedFuncHook(GL2ES2 gl, PMVMatrix matrix,
- Class<?> shaderRootClass, String shaderSrcRoot, String shaderBinRoot,
- String vertexColorFile,
- String vertexColorLightFile,
- String fragmentColorFile,
- String fragmentColorTextureFile) {
- this.gl = gl;
- pmvMatrix = matrix;
+ public boolean verbose() { return fixedFunction.verbose(); }
- fixedFunction = new FixedFuncPipeline(gl, pmvMatrix,
- shaderRootClass, shaderSrcRoot, shaderBinRoot,
- vertexColorFile, vertexColorLightFile, fragmentColorFile, fragmentColorTextureFile);
- }
+ public void setVerbose(final boolean v) { fixedFunction.setVerbose(v); }
public void destroy() {
fixedFunction.destroy(gl);
fixedFunction = null;
+ pmvMatrix=null;
+ gl=null;
}
public PMVMatrix getMatrix() { return pmvMatrix; }
//
- // FixedFuncHookIf - hooks
+ // FixedFuncHookIf - hooks
//
- public void glDrawArrays(int mode, int first, int count) {
- fixedFunction.validate(gl);
- gl.glDrawArrays(mode, first, count);
+ public final boolean isGL4core() {
+ return false;
+ }
+ public final boolean isGL3core() {
+ return false;
+ }
+ public final boolean isGLcore() {
+ return false;
+ }
+ public final boolean isGLES2Compatible() {
+ return false;
}
- public void glDrawElements(int mode, int count, int type, java.nio.Buffer indices) {
- fixedFunction.validate(gl);
- gl.glDrawElements(mode, count, type, indices);
+ public final boolean isGLES3Compatible() {
+ return false;
}
- public void glDrawElements(int mode, int count, int type, long indices_buffer_offset) {
- fixedFunction.validate(gl);
- gl.glDrawElements(mode, count, type, indices_buffer_offset);
+ public final GLProfile getGLProfile() {
+ return gl2es1GLProfile;
+ }
+ public void glDrawArrays(final int mode, final int first, final int count) {
+ fixedFunction.glDrawArrays(gl, mode, first, count);
+ }
+ public void glDrawElements(final int mode, final int count, final int type, final java.nio.Buffer indices) {
+ fixedFunction.glDrawElements(gl, mode, count, type, indices);
+ }
+ public void glDrawElements(final int mode, final int count, final int type, final long indices_buffer_offset) {
+ fixedFunction.glDrawElements(gl, mode, count, type, indices_buffer_offset);
}
- public void glActiveTexture(int texture) {
- fixedFunction.glActiveTexture(gl, texture);
+ public void glActiveTexture(final int texture) {
+ fixedFunction.glActiveTexture(texture);
gl.glActiveTexture(texture);
}
- public void glEnable(int cap) {
- if(fixedFunction.glEnable(gl, cap, true)) {
+ public void glEnable(final int cap) {
+ if(fixedFunction.glEnable(cap, true)) {
gl.glEnable(cap);
}
}
- public void glDisable(int cap) {
- if(fixedFunction.glEnable(gl, cap, false)) {
+ public void glDisable(final int cap) {
+ if(fixedFunction.glEnable(cap, false)) {
gl.glDisable(cap);
}
}
- public void glCullFace(int faceName) {
- fixedFunction.glCullFace(gl, faceName);
- gl.glCullFace(faceName);
- }
-
- public void glGetFloatv(int pname, java.nio.FloatBuffer params) {
+ @Override
+ public void glGetFloatv(final int pname, final java.nio.FloatBuffer params) {
if(PMVMatrix.isMatrixGetName(pname)) {
pmvMatrix.glGetFloatv(pname, params);
return;
}
gl.glGetFloatv(pname, params);
}
- public void glGetFloatv(int pname, float[] params, int params_offset) {
+ @Override
+ public void glGetFloatv(final int pname, final float[] params, final int params_offset) {
if(PMVMatrix.isMatrixGetName(pname)) {
pmvMatrix.glGetFloatv(pname, params, params_offset);
return;
}
gl.glGetFloatv(pname, params, params_offset);
}
- public void glGetIntegerv(int pname, IntBuffer params) {
+ @Override
+ public void glGetIntegerv(final int pname, final IntBuffer params) {
if(PMVMatrix.isMatrixGetName(pname)) {
pmvMatrix.glGetIntegerv(pname, params);
return;
}
gl.glGetIntegerv(pname, params);
}
- public void glGetIntegerv(int pname, int[] params, int params_offset) {
+ @Override
+ public void glGetIntegerv(final int pname, final int[] params, final int params_offset) {
if(PMVMatrix.isMatrixGetName(pname)) {
pmvMatrix.glGetIntegerv(pname, params, params_offset);
return;
@@ -139,95 +189,193 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
gl.glGetIntegerv(pname, params, params_offset);
}
- //
+ public void glTexEnvi(final int target, final int pname, final int value) {
+ fixedFunction.glTexEnvi(target, pname, value);
+ }
+ public void glGetTexEnviv(final int target, final int pname, final IntBuffer params) {
+ fixedFunction.glGetTexEnviv(target, pname, params);
+ }
+ public void glGetTexEnviv(final int target, final int pname, final int[] params, final int params_offset) {
+ fixedFunction.glGetTexEnviv(target, pname, params, params_offset);
+ }
+ public void glBindTexture(final int target, final int texture) {
+ fixedFunction.glBindTexture(target, texture);
+ gl.glBindTexture(target, texture);
+ }
+ public void glTexImage2D(final int target, final int level, int internalformat, final int width, final int height, final int border,
+ final int format, final int type, final Buffer pixels) {
+ // align internalformat w/ format, an ES2 requirement
+ switch(internalformat) {
+ case 3: internalformat= ( GL.GL_RGBA == format ) ? GL.GL_RGBA : GL.GL_RGB; break;
+ case 4: internalformat= ( GL.GL_RGB == format ) ? GL.GL_RGB : GL.GL_RGBA; break;
+ }
+ fixedFunction.glTexImage2D(target, /* level, */ internalformat, /*width, height, border, */ format /*, type, pixels*/);
+ gl.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
+ }
+ public void glTexImage2D(final int target, final int level, int internalformat, final int width, final int height, final int border,
+ final int format, final int type, final long pixels_buffer_offset) {
+ // align internalformat w/ format, an ES2 requirement
+ switch(internalformat) {
+ case 3: internalformat= ( GL.GL_RGBA == format ) ? GL.GL_RGBA : GL.GL_RGB; break;
+ case 4: internalformat= ( GL.GL_RGB == format ) ? GL.GL_RGB : GL.GL_RGBA; break;
+ }
+ fixedFunction.glTexImage2D(target, /* level, */ internalformat, /*width, height, border, */ format /*, type, pixels*/);
+ gl.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
+ }
+
+ public void glPointSize(final float size) {
+ fixedFunction.glPointSize(size);
+ }
+ public void glPointParameterf(final int pname, final float param) {
+ fixedFunction.glPointParameterf(pname, param);
+ }
+ public void glPointParameterfv(final int pname, final float[] params, final int params_offset) {
+ fixedFunction.glPointParameterfv(pname, params, params_offset);
+ }
+ public void glPointParameterfv(final int pname, final java.nio.FloatBuffer params) {
+ fixedFunction.glPointParameterfv(pname, params);
+ }
+
+ //
// MatrixIf
//
public int glGetMatrixMode() {
return pmvMatrix.glGetMatrixMode();
}
- public void glMatrixMode(int mode) {
+ @Override
+ public void glMatrixMode(final int mode) {
pmvMatrix.glMatrixMode(mode);
}
- public void glLoadMatrixf(java.nio.FloatBuffer m) {
+ @Override
+ public void glLoadMatrixf(final java.nio.FloatBuffer m) {
pmvMatrix.glLoadMatrixf(m);
}
- public void glLoadMatrixf(float[] m, int m_offset) {
- glLoadMatrixf(GLBuffers.newDirectFloatBuffer(m, m_offset));
+ @Override
+ public void glLoadMatrixf(final float[] m, final int m_offset) {
+ glLoadMatrixf(Buffers.newDirectFloatBuffer(m, m_offset));
}
+ @Override
public void glPopMatrix() {
pmvMatrix.glPopMatrix();
}
+ @Override
public void glPushMatrix() {
pmvMatrix.glPushMatrix();
}
+ @Override
public void glLoadIdentity() {
pmvMatrix.glLoadIdentity();
}
- public void glMultMatrixf(java.nio.FloatBuffer m) {
+ @Override
+ public void glMultMatrixf(final java.nio.FloatBuffer m) {
pmvMatrix.glMultMatrixf(m);
}
- public void glMultMatrixf(float[] m, int m_offset) {
- glMultMatrixf(GLBuffers.newDirectFloatBuffer(m, m_offset));
+ @Override
+ public void glMultMatrixf(final float[] m, final int m_offset) {
+ glMultMatrixf(Buffers.newDirectFloatBuffer(m, m_offset));
}
- public void glTranslatef(float x, float y, float z) {
+ @Override
+ public void glTranslatef(final float x, final float y, final float z) {
pmvMatrix.glTranslatef(x, y, z);
}
- public void glRotatef(float angdeg, float x, float y, float z) {
+ @Override
+ public void glRotatef(final float angdeg, final float x, final float y, final float z) {
pmvMatrix.glRotatef(angdeg, x, y, z);
}
- public void glScalef(float x, float y, float z) {
+ @Override
+ public void glScalef(final float x, final float y, final float z) {
pmvMatrix.glScalef(x, y, z);
}
- public void glOrthof(float left, float right, float bottom, float top, float zNear, float zFar) {
+ public void glOrtho(final double left, final double right, final double bottom, final double top, final double near_val, final double far_val) {
+ glOrthof((float) left, (float) right, (float) bottom, (float) top, (float) near_val, (float) far_val);
+ }
+ @Override
+ public void glOrthof(final float left, final float right, final float bottom, final float top, final float zNear, final float zFar) {
pmvMatrix.glOrthof(left, right, bottom, top, zNear, zFar);
}
- public void glFrustumf(float left, float right, float bottom, float top, float zNear, float zFar) {
+ public void glFrustum(final double left, final double right, final double bottom, final double top, final double zNear, final double zFar) {
+ glFrustumf((float) left, (float) right, (float) bottom, (float) top, (float) zNear, (float) zFar);
+ }
+ @Override
+ public void glFrustumf(final float left, final float right, final float bottom, final float top, final float zNear, final float zFar) {
pmvMatrix.glFrustumf(left, right, bottom, top, zNear, zFar);
}
- //
+ //
// LightingIf
//
- public void glColor4f(float red, float green, float blue, float alpha) {
- fixedFunction.glColor4fv(gl, GLBuffers.newDirectFloatBuffer(new float[] { red, green, blue, alpha }));
+ @Override
+ public void glColor4f(final float red, final float green, final float blue, final float alpha) {
+ fixedFunction.glColor4f(gl, red, green, blue, alpha);
}
- public void glLightfv(int light, int pname, java.nio.FloatBuffer params) {
+ public void glColor4ub(final byte red, final byte green, final byte blue, final byte alpha) {
+ glColor4f(ValueConv.byte_to_float(red, false),
+ ValueConv.byte_to_float(green, false),
+ ValueConv.byte_to_float(blue, false),
+ ValueConv.byte_to_float(alpha, false) );
+ }
+ @Override
+ public void glLightfv(final int light, final int pname, final java.nio.FloatBuffer params) {
fixedFunction.glLightfv(gl, light, pname, params);
}
- public void glLightfv(int light, int pname, float[] params, int params_offset) {
- glLightfv(light, pname, GLBuffers.newDirectFloatBuffer(params, params_offset));
+ @Override
+ public void glLightfv(final int light, final int pname, final float[] params, final int params_offset) {
+ glLightfv(light, pname, Buffers.newDirectFloatBuffer(params, params_offset));
}
- public void glMaterialfv(int face, int pname, java.nio.FloatBuffer params) {
+ @Override
+ public void glMaterialfv(final int face, final int pname, final java.nio.FloatBuffer params) {
fixedFunction.glMaterialfv(gl, face, pname, params);
}
- public void glMaterialfv(int face, int pname, float[] params, int params_offset) {
- glMaterialfv(face, pname, GLBuffers.newDirectFloatBuffer(params, params_offset));
+ @Override
+ public void glMaterialfv(final int face, final int pname, final float[] params, final int params_offset) {
+ glMaterialfv(face, pname, Buffers.newDirectFloatBuffer(params, params_offset));
}
- public void glMaterialf(int face, int pname, float param) {
- glMaterialfv(face, pname, GLBuffers.newDirectFloatBuffer(new float[] { param }));
+ @Override
+ public void glMaterialf(final int face, final int pname, final float param) {
+ glMaterialfv(face, pname, Buffers.newDirectFloatBuffer(new float[] { param }));
}
- public void glShadeModel(int mode) {
+
+ //
+ // Misc Simple States
+ //
+ @Override
+ public void glShadeModel(final int mode) {
fixedFunction.glShadeModel(gl, mode);
}
+ public void glAlphaFunc(final int func, final float ref) {
+ fixedFunction.glAlphaFunc(func, ref);
+ }
+
+ /** ES2 supports CullFace implicit
+ public void glCullFace(int faceName) {
+ fixedFunction.glCullFace(faceName);
+ gl.glCullFace(faceName);
+ } */
//
// PointerIf
//
- public void glEnableClientState(int glArrayIndex) {
+ public void glClientActiveTexture(final int textureUnit) {
+ fixedFunction.glClientActiveTexture(textureUnit);
+ }
+ @Override
+ public void glEnableClientState(final int glArrayIndex) {
fixedFunction.glEnableClientState(gl, glArrayIndex);
}
- public void glDisableClientState(int glArrayIndex) {
+ @Override
+ public void glDisableClientState(final int glArrayIndex) {
fixedFunction.glDisableClientState(gl, glArrayIndex);
}
- public void glVertexPointer(GLArrayData array) {
+ @Override
+ public void glVertexPointer(final GLArrayData array) {
if(array.isVBO()) {
- if(!gl.glIsVBOArrayEnabled()) {
+ if(!gl.isVBOArrayBound()) {
throw new GLException("VBO array is not enabled: "+array);
}
} else {
- if(gl.glIsVBOArrayEnabled()) {
+ if(gl.isVBOArrayBound()) {
throw new GLException("VBO array is not disabled: "+array);
}
Buffers.rangeCheck(array.getBuffer(), 1);
@@ -237,25 +385,29 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
fixedFunction.glVertexPointer(gl, array);
}
- public void glVertexPointer(int size, int type, int stride, java.nio.Buffer pointer) {
- glVertexPointer(GLArrayDataWrapper.createFixed(GL_VERTEX_ARRAY, size, type, false, stride, pointer, 0, 0, 0, GL.GL_ARRAY_BUFFER));
+ @Override
+ public void glVertexPointer(final int size, final int type, final int stride, final java.nio.Buffer pointer) {
+ glVertexPointer(GLArrayDataWrapper.createFixed(GL_VERTEX_ARRAY, size, type, GLBuffers.isGLTypeFixedPoint(type), stride,
+ pointer, 0, 0, 0, GL.GL_ARRAY_BUFFER));
}
- public void glVertexPointer(int size, int type, int stride, long pointer_buffer_offset) {
- int vboName = gl.glGetBoundBuffer(GL.GL_ARRAY_BUFFER);
+ @Override
+ public void glVertexPointer(final int size, final int type, final int stride, final long pointer_buffer_offset) {
+ final int vboName = gl.getBoundBuffer(GL.GL_ARRAY_BUFFER);
if(vboName==0) {
throw new GLException("no GL_ARRAY_BUFFER VBO bound");
}
- glVertexPointer(GLArrayDataWrapper.createFixed(GL_VERTEX_ARRAY, size, type, false, stride,
+ glVertexPointer(GLArrayDataWrapper.createFixed(GL_VERTEX_ARRAY, size, type, GLBuffers.isGLTypeFixedPoint(type), stride,
null, vboName, pointer_buffer_offset, GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER));
}
- public void glColorPointer(GLArrayData array) {
+ @Override
+ public void glColorPointer(final GLArrayData array) {
if(array.isVBO()) {
- if(!gl.glIsVBOArrayEnabled()) {
+ if(!gl.isVBOArrayBound()) {
throw new GLException("VBO array is not enabled: "+array);
}
} else {
- if(gl.glIsVBOArrayEnabled()) {
+ if(gl.isVBOArrayBound()) {
throw new GLException("VBO array is not disabled: "+array);
}
Buffers.rangeCheck(array.getBuffer(), 1);
@@ -264,29 +416,32 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
}
fixedFunction.glColorPointer(gl, array);
}
- public void glColorPointer(int size, int type, int stride, java.nio.Buffer pointer) {
- glColorPointer(GLArrayDataWrapper.createFixed(GL_COLOR_ARRAY, size, type, false, stride,
+ @Override
+ public void glColorPointer(final int size, final int type, final int stride, final java.nio.Buffer pointer) {
+ glColorPointer(GLArrayDataWrapper.createFixed(GL_COLOR_ARRAY, size, type, GLBuffers.isGLTypeFixedPoint(type), stride,
pointer, 0, 0, 0, GL.GL_ARRAY_BUFFER));
}
- public void glColorPointer(int size, int type, int stride, long pointer_buffer_offset) {
- int vboName = gl.glGetBoundBuffer(GL.GL_ARRAY_BUFFER);
+ @Override
+ public void glColorPointer(final int size, final int type, final int stride, final long pointer_buffer_offset) {
+ final int vboName = gl.getBoundBuffer(GL.GL_ARRAY_BUFFER);
if(vboName==0) {
throw new GLException("no GL_ARRAY_BUFFER VBO bound");
}
- glColorPointer(GLArrayDataWrapper.createFixed(GL_COLOR_ARRAY, size, type, false, stride,
+ glColorPointer(GLArrayDataWrapper.createFixed(GL_COLOR_ARRAY, size, type, GLBuffers.isGLTypeFixedPoint(type), stride,
null, vboName, pointer_buffer_offset, GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER));
}
- public void glNormalPointer(GLArrayData array) {
+ @Override
+ public void glNormalPointer(final GLArrayData array) {
if(array.getComponentCount()!=3) {
throw new GLException("Only 3 components per normal allowed");
}
if(array.isVBO()) {
- if(!gl.glIsVBOArrayEnabled()) {
+ if(!gl.isVBOArrayBound()) {
throw new GLException("VBO array is not enabled: "+array);
}
} else {
- if(gl.glIsVBOArrayEnabled()) {
+ if(gl.isVBOArrayBound()) {
throw new GLException("VBO array is not disabled: "+array);
}
Buffers.rangeCheck(array.getBuffer(), 1);
@@ -295,26 +450,29 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
}
fixedFunction.glNormalPointer(gl, array);
}
- public void glNormalPointer(int type, int stride, java.nio.Buffer pointer) {
- glNormalPointer(GLArrayDataWrapper.createFixed(GL_NORMAL_ARRAY, 3, type, false, stride,
+ @Override
+ public void glNormalPointer(final int type, final int stride, final java.nio.Buffer pointer) {
+ glNormalPointer(GLArrayDataWrapper.createFixed(GL_NORMAL_ARRAY, 3, type, GLBuffers.isGLTypeFixedPoint(type), stride,
pointer, 0, 0, 0, GL.GL_ARRAY_BUFFER));
}
- public void glNormalPointer(int type, int stride, long pointer_buffer_offset) {
- int vboName = gl.glGetBoundBuffer(GL.GL_ARRAY_BUFFER);
+ @Override
+ public void glNormalPointer(final int type, final int stride, final long pointer_buffer_offset) {
+ final int vboName = gl.getBoundBuffer(GL.GL_ARRAY_BUFFER);
if(vboName==0) {
throw new GLException("no GL_ARRAY_BUFFER VBO bound");
}
- glNormalPointer(GLArrayDataWrapper.createFixed(GL_NORMAL_ARRAY, 3, type, false, stride,
+ glNormalPointer(GLArrayDataWrapper.createFixed(GL_NORMAL_ARRAY, 3, type, GLBuffers.isGLTypeFixedPoint(type), stride,
null, vboName, pointer_buffer_offset, GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER));
}
- public void glTexCoordPointer(GLArrayData array) {
+ @Override
+ public void glTexCoordPointer(final GLArrayData array) {
if(array.isVBO()) {
- if(!gl.glIsVBOArrayEnabled()) {
+ if(!gl.isVBOArrayBound()) {
throw new GLException("VBO array is not enabled: "+array);
}
} else {
- if(gl.glIsVBOArrayEnabled()) {
+ if(gl.isVBOArrayBound()) {
throw new GLException("VBO array is not disabled: "+array);
}
Buffers.rangeCheck(array.getBuffer(), 1);
@@ -323,25 +481,29 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
}
fixedFunction.glTexCoordPointer(gl, array);
}
- public void glTexCoordPointer(int size, int type, int stride, java.nio.Buffer pointer) {
+ @Override
+ public void glTexCoordPointer(final int size, final int type, final int stride, final java.nio.Buffer pointer) {
glTexCoordPointer(
- GLArrayDataWrapper.createFixed(GL_TEXTURE_COORD_ARRAY, size, type, false, stride, pointer, 0, 0, 0, GL.GL_ARRAY_BUFFER));
+ GLArrayDataWrapper.createFixed(GL_TEXTURE_COORD_ARRAY, size, type, GLBuffers.isGLTypeFixedPoint(type), stride,
+ pointer, 0, 0, 0, GL.GL_ARRAY_BUFFER));
}
- public void glTexCoordPointer(int size, int type, int stride, long pointer_buffer_offset) {
- int vboName = gl.glGetBoundBuffer(GL.GL_ARRAY_BUFFER);
+ @Override
+ public void glTexCoordPointer(final int size, final int type, final int stride, final long pointer_buffer_offset) {
+ final int vboName = gl.getBoundBuffer(GL.GL_ARRAY_BUFFER);
if(vboName==0) {
throw new GLException("no GL_ARRAY_BUFFER VBO bound");
}
glTexCoordPointer(
- GLArrayDataWrapper.createFixed(GL_TEXTURE_COORD_ARRAY, size, type, false, stride,
+ GLArrayDataWrapper.createFixed(GL_TEXTURE_COORD_ARRAY, size, type, GLBuffers.isGLTypeFixedPoint(type), stride,
null, vboName, pointer_buffer_offset, GL.GL_STATIC_DRAW, GL.GL_ARRAY_BUFFER) );
}
+ @Override
public final String toString() {
- StringBuilder buf = new StringBuilder();
+ final StringBuilder buf = new StringBuilder();
buf.append(getClass().getName()+" (");
if(null!=pmvMatrix) {
- buf.append(", matrixDirty: "+pmvMatrix.isDirty());
+ buf.append(", matrixDirty: "+ (0 != pmvMatrix.getModifiedBits(false)));
}
buf.append("\n\t, FixedFunction: "+fixedFunction);
buf.append(gl);
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncPipeline.java b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncPipeline.java
index bfe2e13c2..7371b0f3b 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncPipeline.java
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncPipeline.java
@@ -29,33 +29,88 @@
package jogamp.opengl.util.glsl.fixedfunc;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2;
+import javax.media.opengl.GL2ES1;
+import javax.media.opengl.GL2ES2;
+import javax.media.opengl.GL2GL3;
+import javax.media.opengl.GLArrayData;
+import javax.media.opengl.GLES2;
+import javax.media.opengl.GLException;
+import javax.media.opengl.GLRunnable2;
+import javax.media.opengl.GLUniformData;
+import javax.media.opengl.fixedfunc.GLLightingFunc;
+import javax.media.opengl.fixedfunc.GLPointerFunc;
+import javax.media.opengl.fixedfunc.GLPointerFuncUtil;
+
+import jogamp.opengl.Debug;
+
import com.jogamp.common.nio.Buffers;
-import javax.media.opengl.*;
-import javax.media.opengl.fixedfunc.*;
-import com.jogamp.opengl.util.*;
-import com.jogamp.opengl.util.glsl.*;
-import java.nio.*;
+import com.jogamp.common.util.IntIntHashMap;
+import com.jogamp.common.util.PropertyAccess;
+import com.jogamp.opengl.util.PMVMatrix;
+import com.jogamp.opengl.util.glsl.ShaderCode;
+import com.jogamp.opengl.util.glsl.ShaderProgram;
+import com.jogamp.opengl.util.glsl.ShaderState;
+import com.jogamp.opengl.util.glsl.fixedfunc.ShaderSelectionMode;
+/**
+ *
+ * <p>
+ * Note: Certain GL FFP state values (e.g.: alphaTestFunc and cullFace)
+ * are mapped to a lower number range so they can be stored in low precision storage,
+ * i.e. in a 'lowp int' (GL ES2).
+ * </p>
+ */
public class FixedFuncPipeline {
+ protected static final boolean DEBUG;
+
+ static {
+ Debug.initSingleton();
+ DEBUG = PropertyAccess.isPropertyDefined("jogl.debug.FixedFuncPipeline", true);
+ }
+
+ /** The maximum texture units which could be used, depending on {@link ShaderSelectionMode}. */
public static final int MAX_TEXTURE_UNITS = 8;
public static final int MAX_LIGHTS = 8;
- public FixedFuncPipeline(GL2ES2 gl, PMVMatrix pmvMatrix) {
- init(gl, pmvMatrix, FixedFuncPipeline.class, shaderSrcRootDef, shaderBinRootDef,
- vertexColorFileDef, vertexColorLightFileDef, fragmentColorFileDef, fragmentColorTextureFileDef);
+ public FixedFuncPipeline(final GL2ES2 gl, final ShaderSelectionMode mode, final PMVMatrix pmvMatrix) {
+ shaderRootClass = FixedFuncPipeline.class;
+ shaderSrcRoot = shaderSrcRootDef;
+ shaderBinRoot = shaderBinRootDef;
+ vertexColorFile = vertexColorFileDef;
+ vertexColorLightFile = vertexColorLightFileDef;
+ fragmentColorFile = fragmentColorFileDef;
+ fragmentColorTextureFile = fragmentColorTextureFileDef;
+ init(gl, mode, pmvMatrix);
}
- public FixedFuncPipeline(GL2ES2 gl, PMVMatrix pmvMatrix, Class shaderRootClass, String shaderSrcRoot, String shaderBinRoot,
- String vertexColorFile,
- String vertexColorLightFile,
- String fragmentColorFile,
- String fragmentColorTextureFile) {
- init(gl, pmvMatrix, shaderRootClass, shaderSrcRoot, shaderBinRoot,
- vertexColorFile, vertexColorLightFile, fragmentColorFile, fragmentColorTextureFile);
+ public FixedFuncPipeline(final GL2ES2 gl, final ShaderSelectionMode mode, final PMVMatrix pmvMatrix,
+ final Class<?> shaderRootClass, final String shaderSrcRoot,
+ final String shaderBinRoot,
+ final String vertexColorFile, final String vertexColorLightFile,
+ final String fragmentColorFile, final String fragmentColorTextureFile) {
+ this.shaderRootClass = shaderRootClass;
+ this.shaderSrcRoot = shaderSrcRoot;
+ this.shaderBinRoot = shaderBinRoot;
+ this.vertexColorFile = vertexColorFile;
+ this.vertexColorLightFile = vertexColorLightFile;
+ this.fragmentColorFile = fragmentColorFile;
+ this.fragmentColorTextureFile = fragmentColorTextureFile;
+ init(gl, mode, pmvMatrix);
}
+ public ShaderSelectionMode getShaderSelectionMode() { return requestedShaderSelectionMode; }
+ public void setShaderSelectionMode(final ShaderSelectionMode mode) { requestedShaderSelectionMode=mode; }
+ public ShaderSelectionMode getCurrentShaderSelectionMode() { return currentShaderSelectionMode; }
+
public boolean verbose() { return verbose; }
- public void setVerbose(boolean v) { verbose=v; }
+ public void setVerbose(final boolean v) { verbose = DEBUG || v; }
public boolean isValid() {
return shaderState.linked();
@@ -69,80 +124,326 @@ public class FixedFuncPipeline {
return activeTextureUnit;
}
- public String getArrayIndexName(int glArrayIndex) {
- String name = GLPointerFuncUtil.getPredefinedArrayIndexName(glArrayIndex);
- switch(glArrayIndex) {
- case GLPointerFunc.GL_VERTEX_ARRAY:
- case GLPointerFunc.GL_NORMAL_ARRAY:
- case GLPointerFunc.GL_COLOR_ARRAY:
- break;
- case GLPointerFunc.GL_TEXTURE_COORD_ARRAY:
- name = name + activeTextureUnit;
- }
- return name;
- }
-
- public void destroy(GL2ES2 gl) {
- shaderProgramColor.release(gl, true);
- shaderProgramColorLight.release(gl, true);
- shaderProgramColorTexture.release(gl, true);
- shaderProgramColorTextureLight.release(gl, true);
+ public void destroy(final GL2ES2 gl) {
+ if(null != shaderProgramColor) {
+ shaderProgramColor.release(gl, true);
+ }
+ if(null != shaderProgramColorLight) {
+ shaderProgramColorLight.release(gl, true);
+ }
+ if(null != shaderProgramColorTexture2) {
+ shaderProgramColorTexture2.release(gl, true);
+ }
+ if(null != shaderProgramColorTexture4) {
+ shaderProgramColorTexture4.release(gl, true);
+ }
+ if(null != shaderProgramColorTexture4) {
+ shaderProgramColorTexture4.release(gl, true);
+ }
+ if(null != shaderProgramColorTexture8Light) {
+ shaderProgramColorTexture8Light.release(gl, true);
+ }
shaderState.destroy(gl);
}
- public void glEnableClientState(GL2ES2 gl, int glArrayIndex) {
- shaderState.useProgram(gl, true);
+ //
+ // Simple Globals
+ //
+ public void glColor4f(final GL2ES2 gl, final float red, final float green, final float blue, final float alpha) {
+ colorStatic.put(0, red);
+ colorStatic.put(1, green);
+ colorStatic.put(2, blue);
+ colorStatic.put(3, alpha);
- shaderState.enableVertexAttribArray(gl, getArrayIndexName(glArrayIndex));
- // textureCoordsEnabled |= (1 << activeTextureUnit);
- if ( textureCoordsEnabled.get(activeTextureUnit) != 1 ) {
- textureCoordsEnabled.put(activeTextureUnit, 1);
- textureCoordsEnabledDirty = true;
+ shaderState.useProgram(gl, true);
+ final GLUniformData ud = shaderState.getUniform(mgl_ColorStatic);
+ if(null!=ud) {
+ // same data object ..
+ shaderState.uniform(gl, ud);
+ } else {
+ throw new GLException("Failed to update: mgl_ColorStatic");
}
}
- public void glDisableClientState(GL2ES2 gl, int glArrayIndex) {
- shaderState.useProgram(gl, true);
+ //
+ // Arrays / States
+ //
+
+ public void glEnableClientState(final GL2ES2 gl, final int glArrayIndex) {
+ glToggleClientState(gl, glArrayIndex, true);
+ }
+
+ public void glDisableClientState(final GL2ES2 gl, final int glArrayIndex) {
+ glToggleClientState(gl, glArrayIndex, false);
+ }
- shaderState.disableVertexAttribArray(gl, getArrayIndexName(glArrayIndex));
- // textureCoordsEnabled &= ~(1 << activeTextureUnit);
- if ( textureCoordsEnabled.get(activeTextureUnit) != 0 ) {
- textureCoordsEnabled.put(activeTextureUnit, 0);
- textureCoordsEnabledDirty = true;
+ private void glToggleClientState(final GL2ES2 gl, final int glArrayIndex, final boolean enable) {
+ final String arrayName = GLPointerFuncUtil.getPredefinedArrayIndexName(glArrayIndex, clientActiveTextureUnit);
+ if(null == arrayName) {
+ throw new GLException("arrayIndex "+toHexString(glArrayIndex)+" unknown");
+ }
+ shaderState.useProgram(gl, true);
+ if(enable) {
+ shaderState.enableVertexAttribArray(gl, arrayName );
+ } else {
+ shaderState.disableVertexAttribArray(gl, arrayName );
+ }
+ switch( glArrayIndex ) {
+ case GLPointerFunc.GL_TEXTURE_COORD_ARRAY:
+ final int enableV = enable ? 1 : 0;
+ // enable-bitwise: textureCoordsEnabled |= (1 << clientActiveTextureUnit);
+ // disable-bitwise: textureCoordsEnabled &= ~(1 << clientActiveTextureUnit);
+ if ( textureCoordEnabled.get(clientActiveTextureUnit) != enableV) {
+ textureCoordEnabled.put(clientActiveTextureUnit, enableV);
+ textureCoordEnabledDirty = true;
+ }
+ break;
+ case GLPointerFunc.GL_COLOR_ARRAY:
+ colorVAEnabledDirty = true;
+ break;
}
}
- public void glVertexPointer(GL2ES2 gl, GLArrayData data) {
+ public void glVertexPointer(final GL2ES2 gl, final GLArrayData data) {
shaderState.useProgram(gl, true);
shaderState.vertexAttribPointer(gl, data);
}
- public void glColorPointer(GL2ES2 gl, GLArrayData data) {
+ public void glColorPointer(final GL2ES2 gl, final GLArrayData data) {
shaderState.useProgram(gl, true);
shaderState.vertexAttribPointer(gl, data);
}
- public void glColor4fv(GL2ES2 gl, FloatBuffer data ) {
+ public void glNormalPointer(final GL2ES2 gl, final GLArrayData data) {
shaderState.useProgram(gl, true);
- GLUniformData ud = shaderState.getUniform(mgl_ColorStatic);
- if(null!=ud) {
- ud.setData(data);
- shaderState.uniform(gl, ud);
+ shaderState.vertexAttribPointer(gl, data);
+ }
+
+ //
+ // MULTI-TEXTURE
+ //
+
+ /** Enables/Disables the named texture unit (if changed), returns previous state */
+ private boolean glEnableTexture(final boolean enable, final int unit) {
+ final boolean isEnabled = 0 != ( textureEnabledBits & ( 1 << activeTextureUnit ) );
+ if( isEnabled != enable ) {
+ if(enable) {
+ textureEnabledBits |= ( 1 << unit );
+ textureEnabled.put(unit, 1);
+ } else {
+ textureEnabledBits &= ~( 1 << unit );
+ textureEnabled.put(unit, 0);
+ }
+ textureEnabledDirty=true;
}
+ return isEnabled;
}
- public void glNormalPointer(GL2ES2 gl, GLArrayData data) {
- shaderState.useProgram(gl, true);
- shaderState.vertexAttribPointer(gl, data);
+ public void glClientActiveTexture(int textureUnit) {
+ textureUnit -= GL.GL_TEXTURE0;
+ if(0 <= textureUnit && textureUnit<MAX_TEXTURE_UNITS) {
+ clientActiveTextureUnit = textureUnit;
+ } else {
+ throw new GLException("glClientActiveTexture textureUnit not within GL_TEXTURE0 + [0.."+MAX_TEXTURE_UNITS+"]: "+textureUnit);
+ }
}
- public void glTexCoordPointer(GL2ES2 gl, GLArrayData data) {
+ public void glActiveTexture(int textureUnit) {
+ textureUnit -= GL.GL_TEXTURE0;
+ if(0 <= textureUnit && textureUnit<MAX_TEXTURE_UNITS) {
+ activeTextureUnit = textureUnit;
+ } else {
+ throw new GLException("glActivateTexture textureUnit not within GL_TEXTURE0 + [0.."+MAX_TEXTURE_UNITS+"]: "+textureUnit);
+ }
+ }
+
+ public void glTexCoordPointer(final GL2ES2 gl, final GLArrayData data) {
+ if( GLPointerFunc.GL_TEXTURE_COORD_ARRAY != data.getIndex() ) {
+ throw new GLException("Invalid GLArrayData Index "+toHexString(data.getIndex())+", "+data);
+ }
shaderState.useProgram(gl, true);
- data.setName( getArrayIndexName(data.getIndex()) );
+ data.setName( GLPointerFuncUtil.getPredefinedArrayIndexName(data.getIndex(), clientActiveTextureUnit) ) ;
shaderState.vertexAttribPointer(gl, data);
}
- public void glLightfv(GL2ES2 gl, int light, int pname, java.nio.FloatBuffer params) {
+ public void glBindTexture(final int target, final int texture) {
+ if(GL.GL_TEXTURE_2D == target) {
+ if( texture != boundTextureObject[activeTextureUnit] ) {
+ boundTextureObject[activeTextureUnit] = texture;
+ textureFormatDirty = true;
+ }
+ } else {
+ System.err.println("FixedFuncPipeline: Unimplemented glBindTexture for target "+toHexString(target)+". Texture name "+toHexString(texture));
+ }
+ }
+
+ public void glTexImage2D(final int target, /* int level, */ final int internalformat, /*, int width, int height, int border, */
+ final int format /*, int type, Buffer pixels */) {
+ final int ifmt;
+ if(GL.GL_TEXTURE_2D == target) {
+ switch(internalformat) {
+ case 3:
+ case GL.GL_RGB:
+ case GL.GL_RGB565:
+ case GL.GL_RGB8:
+ case GL.GL_RGB10:
+ ifmt = 3;
+ break;
+ case 4:
+ case GL.GL_RGBA:
+ case GL.GL_RGB5_A1:
+ case GL.GL_RGBA4:
+ case GL.GL_RGBA8:
+ case GL.GL_RGB10_A2:
+ ifmt = 4;
+ break;
+ default:
+ System.err.println("FixedFuncPipeline: glTexImage2D TEXTURE_2D: Unimplemented internalformat "+toHexString(internalformat));
+ ifmt = 4;
+ break;
+ }
+ if( ifmt != texID2Format.put(boundTextureObject[activeTextureUnit], ifmt) ) {
+ textureFormatDirty = true;
+ // System.err.println("glTexImage2D TEXTURE_2D: internalformat ifmt "+toHexString(internalformat)+" fmt "+toHexString(format)+" -> "+toHexString(ifmt));
+ }
+ } else {
+ System.err.println("FixedFuncPipeline: Unimplemented glTexImage2D: target "+toHexString(target)+", internalformat "+toHexString(internalformat));
+ }
+ }
+ /*
+ public void glTexImage2D(int target, int level, int internalformat, int width, int height, int border,
+ int format, int type, long pixels_buffer_offset) {
+ textureFormat.put(activeTextureUnit, internalformat);
+ textureFormatDirty = true;
+ }*/
+
+ public void glTexEnvi(final int target, final int pname, final int value) {
+ if(GL2ES1.GL_TEXTURE_ENV == target && GL2ES1.GL_TEXTURE_ENV_MODE == pname) {
+ final int mode;
+ switch( value ) {
+ case GL2ES1.GL_ADD:
+ mode = 1;
+ break;
+ case GL2ES1.GL_MODULATE:
+ mode = 2;
+ break;
+ case GL2ES1.GL_DECAL:
+ mode = 3;
+ break;
+ case GL.GL_BLEND:
+ mode = 4;
+ break;
+ case GL.GL_REPLACE:
+ mode = 5;
+ break;
+ case GL2ES1.GL_COMBINE:
+ mode = 2; // FIXME
+ System.err.println("FixedFuncPipeline: glTexEnv GL_TEXTURE_ENV_MODE: unimplemented mode: "+toHexString(value));
+ break;
+ default:
+ throw new GLException("glTexEnv GL_TEXTURE_ENV_MODE: invalid mode: "+toHexString(value));
+ }
+ setTextureEnvMode(mode);
+ } else if(verbose) {
+ System.err.println("FixedFuncPipeline: Unimplemented TexEnv: target "+toHexString(target)+", pname "+toHexString(pname)+", mode: "+toHexString(value));
+ }
+ }
+ private void setTextureEnvMode(final int value) {
+ if( value != textureEnvMode.get(activeTextureUnit) ) {
+ textureEnvMode.put(activeTextureUnit, value);
+ textureEnvModeDirty = true;
+ }
+ }
+ public void glGetTexEnviv(final int target, final int pname, final IntBuffer params) { // FIXME
+ System.err.println("FixedFuncPipeline: Unimplemented glGetTexEnviv: target "+toHexString(target)+", pname "+toHexString(pname));
+ }
+ public void glGetTexEnviv(final int target, final int pname, final int[] params, final int params_offset) { // FIXME
+ System.err.println("FixedFuncPipeline: Unimplemented glGetTexEnviv: target "+toHexString(target)+", pname "+toHexString(pname));
+ }
+
+ //
+ // Point Sprites
+ //
+ public void glPointSize(final float size) {
+ pointParams.put(0, size);
+ pointParamsDirty = true;
+ }
+ public void glPointParameterf(final int pname, final float param) {
+ switch(pname) {
+ case GL2ES1.GL_POINT_SIZE_MIN:
+ pointParams.put(2, param);
+ break;
+ case GL2ES1.GL_POINT_SIZE_MAX:
+ pointParams.put(3, param);
+ break;
+ case GL.GL_POINT_FADE_THRESHOLD_SIZE:
+ pointParams.put(4+3, param);
+ break;
+ }
+ pointParamsDirty = true;
+ }
+ public void glPointParameterfv(final int pname, final float[] params, final int params_offset) {
+ switch(pname) {
+ case GL2ES1.GL_POINT_DISTANCE_ATTENUATION:
+ pointParams.put(4+0, params[params_offset + 0]);
+ pointParams.put(4+1, params[params_offset + 1]);
+ pointParams.put(4+2, params[params_offset + 2]);
+ break;
+ }
+ pointParamsDirty = true;
+ }
+ public void glPointParameterfv(final int pname, final java.nio.FloatBuffer params) {
+ final int o = params.position();
+ switch(pname) {
+ case GL2ES1.GL_POINT_DISTANCE_ATTENUATION:
+ pointParams.put(4+0, params.get(o + 0));
+ pointParams.put(4+1, params.get(o + 1));
+ pointParams.put(4+2, params.get(o + 2));
+ break;
+ }
+ pointParamsDirty = true;
+ }
+
+ // private int[] pointTexObj = new int[] { 0 };
+
+ private void glDrawPoints(final GL2ES2 gl, final GLRunnable2<Object,Object> glDrawAction, final Object args) {
+ if(gl.isGL2GL3()) {
+ gl.glEnable(GL2GL3.GL_VERTEX_PROGRAM_POINT_SIZE);
+ }
+ if(gl.isGL2ES1()) {
+ gl.glEnable(GL2ES1.GL_POINT_SPRITE);
+ }
+ loadShaderPoints(gl);
+ shaderState.attachShaderProgram(gl, shaderProgramPoints, true);
+ validate(gl, false); // sync uniforms
+
+ glDrawAction.run(gl, args);
+
+ if(gl.isGL2ES1()) {
+ gl.glDisable(GL2ES1.GL_POINT_SPRITE);
+ }
+ if(gl.isGL2GL3()) {
+ gl.glDisable(GL2GL3.GL_VERTEX_PROGRAM_POINT_SIZE);
+ }
+ shaderState.attachShaderProgram(gl, selectShaderProgram(gl, currentShaderSelectionMode), true);
+ }
+ private static final GLRunnable2<Object, Object> glDrawArraysAction = new GLRunnable2<Object,Object>() {
+ @Override
+ public Object run(final GL gl, final Object args) {
+ final int[] _args = (int[])args;
+ gl.glDrawArrays(GL.GL_POINTS, _args[0], _args[1]);
+ return null;
+ }
+ };
+ private final void glDrawPointArrays(final GL2ES2 gl, final int first, final int count) {
+ glDrawPoints(gl, glDrawArraysAction, new int[] { first, count });
+ }
+
+ //
+ // Lighting
+ //
+
+ public void glLightfv(final GL2ES2 gl, int light, final int pname, final java.nio.FloatBuffer params) {
shaderState.useProgram(gl, true);
light -=GLLightingFunc.GL_LIGHT0;
if(0 <= light && light < MAX_LIGHTS) {
@@ -179,21 +480,18 @@ public class FixedFuncPipeline {
ud = shaderState.getUniform(mgl_LightSource+"["+light+"].quadraticAttenuation");
break;
default:
- if(verbose) {
- System.err.println("glLightfv pname not within [GL_AMBIENT GL_DIFFUSE GL_SPECULAR GL_POSITION GL_SPOT_DIRECTION]: "+pname);
- }
- return;
+ throw new GLException("glLightfv invalid pname: "+toHexString(pname));
}
if(null!=ud) {
ud.setData(params);
shaderState.uniform(gl, ud);
}
- } else if(verbose) {
- System.err.println("glLightfv light not within [0.."+MAX_LIGHTS+"]: "+light);
+ } else {
+ throw new GLException("glLightfv light not within [0.."+MAX_LIGHTS+"]: "+light);
}
}
- public void glMaterialfv(GL2ES2 gl, int face, int pname, java.nio.FloatBuffer params) {
+ public void glMaterialfv(final GL2ES2 gl, final int face, final int pname, final java.nio.FloatBuffer params) {
shaderState.useProgram(gl, true);
switch (face) {
@@ -201,10 +499,8 @@ public class FixedFuncPipeline {
case GL.GL_FRONT_AND_BACK:
break;
case GL.GL_BACK:
- if(verbose) {
- System.err.println("glMaterialfv face GL_BACK currently not supported");
- }
- break;
+ System.err.println("FixedFuncPipeline: Unimplemented glMaterialfv GL_BACK face");
+ return;
default:
}
@@ -214,7 +510,13 @@ public class FixedFuncPipeline {
ud = shaderState.getUniform(mgl_FrontMaterial+".ambient");
break;
case GLLightingFunc.GL_AMBIENT_AND_DIFFUSE:
- glMaterialfv(gl, face, GLLightingFunc.GL_AMBIENT, params);
+ {
+ ud = shaderState.getUniform(mgl_FrontMaterial+".ambient");
+ if(null!=ud) {
+ ud.setData(params);
+ shaderState.uniform(gl, ud);
+ }
+ }
// fall through intended ..
case GLLightingFunc.GL_DIFFUSE:
ud = shaderState.getUniform(mgl_FrontMaterial+".diffuse");
@@ -229,69 +531,161 @@ public class FixedFuncPipeline {
ud = shaderState.getUniform(mgl_FrontMaterial+".shininess");
break;
default:
- if(verbose) {
- System.err.println("glMaterialfv pname not within [GL_AMBIENT GL_DIFFUSE GL_SPECULAR GL_EMISSION GL_SHININESS]: "+pname);
- }
- return;
+ throw new GLException("glMaterialfv invalid pname: "+toHexString(pname));
}
if(null!=ud) {
ud.setData(params);
shaderState.uniform(gl, ud);
+ } else if(verbose) {
+
}
}
- public void glShadeModel(GL2ES2 gl, int mode) {
+ //
+ // Misc States
+ //
+
+ public void glShadeModel(final GL2ES2 gl, final int mode) {
shaderState.useProgram(gl, true);
- GLUniformData ud = shaderState.getUniform(mgl_ShadeModel);
+ final GLUniformData ud = shaderState.getUniform(mgl_ShadeModel);
if(null!=ud) {
ud.setData(mode);
shaderState.uniform(gl, ud);
}
}
- public void glActiveTexture(GL2ES2 gl, int textureUnit) {
- textureUnit -= GL.GL_TEXTURE0;
- if(0 <= textureUnit && textureUnit<MAX_TEXTURE_UNITS) {
- shaderState.useProgram(gl, true);
- GLUniformData ud;
- ud = shaderState.getUniform(mgl_ActiveTexture);
- if(null!=ud) {
- ud.setData(textureUnit);
- shaderState.uniform(gl, ud);
+ /** ES2 supports CullFace implicit
+ public void glCullFace(int faceName) {
+ int _cullFace;
+ switch(faceName) {
+ case GL.GL_FRONT:
+ _cullFace = 1;
+ break;
+ case GL.GL_BACK:
+ _cullFace = 2;
+ break;
+ case GL.GL_FRONT_AND_BACK:
+ _cullFace = 3;
+ break;
+ default:
+ throw new GLException("glCullFace invalid faceName: "+toHexString(faceName));
+ }
+ if(0 < _cullFace) {
+ if(0>cullFace) {
+ _cullFace *= -1;
}
- ud = shaderState.getUniform(mgl_ActiveTextureIdx);
- if(null!=ud) {
- ud.setData(textureUnit);
- shaderState.uniform(gl, ud);
+ if(cullFace != _cullFace) {
+ cullFace = _cullFace;
+ cullFaceDirty=true;
+ }
+ }
+ } */
+
+ public void glAlphaFunc(final int func, final float ref) {
+ int _func;
+ switch(func) {
+ case GL.GL_NEVER:
+ _func = 1;
+ break;
+ case GL.GL_LESS:
+ _func = 2;
+ break;
+ case GL.GL_EQUAL:
+ _func = 3;
+ break;
+ case GL.GL_LEQUAL:
+ _func = 4;
+ break;
+ case GL.GL_GREATER:
+ _func = 5;
+ break;
+ case GL.GL_NOTEQUAL:
+ _func = 6;
+ break;
+ case GL.GL_GEQUAL:
+ _func = 7;
+ break;
+ case GL.GL_ALWAYS:
+ _func = 8;
+ break;
+ default:
+ throw new GLException("glAlphaFunc invalid func: "+toHexString(func));
+ }
+ if(0 < _func) {
+ if(0>alphaTestFunc) {
+ _func *= -1;
+ }
+ if( alphaTestFunc != _func || alphaTestRef != ref ) {
+ alphaTestFunc = _func;
+ alphaTestRef = ref;
+ alphaTestDirty=true;
}
- activeTextureUnit = textureUnit;
- } else {
- throw new GLException("glActivateTexture textureUnit not within GL_TEXTURE0 + [0.."+MAX_TEXTURE_UNITS+"]: "+textureUnit);
}
}
/**
- * @return false if digested in regard to GL2ES2 spec,
+ * @return false if digested in regard to GL2ES2 spec,
* eg this call must not be passed to an underlying ES2 implementation.
* true if this call shall be passed to an underlying GL2ES2/ES2 implementation as well.
*/
- public boolean glEnable(GL2ES2 gl, int cap, boolean enable) {
+ public boolean glEnable(final int cap, final boolean enable) {
switch(cap) {
- case GL.GL_TEXTURE_2D:
- textureEnabled=enable;
+ case GL.GL_BLEND:
+ case GL.GL_DEPTH_TEST:
+ case GL.GL_DITHER:
+ case GL.GL_POLYGON_OFFSET_FILL:
+ case GL.GL_SAMPLE_ALPHA_TO_COVERAGE:
+ case GL.GL_SAMPLE_COVERAGE:
+ case GL.GL_SCISSOR_TEST:
+ case GL.GL_STENCIL_TEST:
return true;
+
+ case GL.GL_CULL_FACE:
+ /** ES2 supports CullFace implicit
+ final int _cullFace;
+ if(0>cullFace && enable || 0<cullFace && !enable) {
+ _cullFace = cullFace * -1;
+ } else {
+ _cullFace = cullFace;
+ }
+ if(_cullFace != cullFace) {
+ cullFaceDirty=true;
+ cullFace=_cullFace;
+ } */
+ return true;
+
+ case GL.GL_TEXTURE_2D:
+ glEnableTexture(enable, activeTextureUnit);
+ return false;
+
case GLLightingFunc.GL_LIGHTING:
lightingEnabled=enable;
return false;
- case GL.GL_CULL_FACE:
- cullFace=Math.abs(cullFace);
- if(!enable) {
- cullFace*=-1;
+
+ case GL2ES1.GL_ALPHA_TEST:
+ final int _alphaTestFunc;
+ if(0>alphaTestFunc && enable || 0<alphaTestFunc && !enable) {
+ _alphaTestFunc = alphaTestFunc * -1;
+ } else {
+ _alphaTestFunc = alphaTestFunc;
}
- return true;
+ if(_alphaTestFunc != alphaTestFunc) {
+ alphaTestDirty=true;
+ alphaTestFunc=_alphaTestFunc;
+ }
+ return false;
+
+ case GL2ES1.GL_POINT_SMOOTH:
+ pointParams.put(1, enable ? 1.0f : 0.0f);
+ pointParamsDirty = true;
+ return false;
+
+ case GL2ES1.GL_POINT_SPRITE:
+ // gl_PointCoord always enabled
+ return false;
}
- int light = cap - GLLightingFunc.GL_LIGHT0;
+ final int light = cap - GLLightingFunc.GL_LIGHT0;
if(0 <= light && light < MAX_LIGHTS) {
if ( (lightsEnabled.get(light)==1) != enable ) {
lightsEnabled.put(light, enable?1:0);
@@ -299,157 +693,422 @@ public class FixedFuncPipeline {
return false;
}
}
- return true; // pass it on ..
+ System.err.println("FixedFunctionPipeline: "+(enable ? "glEnable" : "glDisable")+" "+toHexString(cap)+" not handled in emulation and not supported in ES2");
+ return false; // ignore!
}
- public void glCullFace(GL2ES2 gl, int faceName) {
- switch(faceName) {
- case GL.GL_FRONT:
- faceName = 1; break;
- case GL.GL_BACK:
- faceName = 2; break;
- case GL.GL_FRONT_AND_BACK:
- faceName = 3; break;
+ //
+ // Draw
+ //
+
+ public void glDrawArrays(final GL2ES2 gl, int mode, final int first, final int count) {
+ switch(mode) {
+ case GL2.GL_QUAD_STRIP:
+ mode=GL.GL_TRIANGLE_STRIP;
+ break;
+ case GL2.GL_POLYGON:
+ mode=GL.GL_TRIANGLE_FAN;
+ break;
+ case GL.GL_POINTS:
+ glDrawPointArrays(gl, first, count);
+ return;
}
- if(0>cullFace) {
- faceName *= -1;
+ validate(gl, true);
+ if ( GL2GL3.GL_QUADS == mode && !gl.isGL2() ) {
+ for (int j = first; j < count - 3; j += 4) {
+ gl.glDrawArrays(GL.GL_TRIANGLE_FAN, j, 4);
+ }
+ } else {
+ gl.glDrawArrays(mode, first, count);
}
- cullFace = faceName;
}
+ public void glDrawElements(final GL2ES2 gl, final int mode, final int count, final int type, final java.nio.Buffer indices) {
+ validate(gl, true);
+ if ( GL2GL3.GL_QUADS == mode && !gl.isGL2() ) {
+ final int idx0 = indices.position();
+
+ if( GL.GL_UNSIGNED_BYTE == type ) {
+ final ByteBuffer b = (ByteBuffer) indices;
+ for (int j = 0; j < count; j++) {
+ gl.glDrawArrays(GL.GL_TRIANGLE_FAN, 0x000000ff & b.get(idx0+j), 4);
+ }
+ } else if( GL.GL_UNSIGNED_SHORT == type ){
+ final ShortBuffer b = (ShortBuffer) indices;
+ for (int j = 0; j < count; j++) {
+ gl.glDrawArrays(GL.GL_TRIANGLE_FAN, 0x0000ffff & b.get(idx0+j), 4);
+ }
+ } else {
+ final IntBuffer b = (IntBuffer) indices;
+ for (int j = 0; j < count; j++) {
+ gl.glDrawArrays(GL.GL_TRIANGLE_FAN, 0xffffffff & b.get(idx0+j), 4);
+ }
+ }
+ } else {
+ // FIXME: Impl. VBO usage .. or unroll (see above)!
+ if( !gl.getContext().isCPUDataSourcingAvail() ) {
+ throw new GLException("CPU data sourcing n/a w/ "+gl.getContext());
+ }
+ // if( GL.GL_POINTS != mode ) {
+ ((GLES2)gl).glDrawElements(mode, count, type, indices);
+ /* } else {
+ // FIXME GL_POINTS !
+ ((GLES2)gl).glDrawElements(mode, count, type, indices);
+ } */
+ }
+ }
+ public void glDrawElements(final GL2ES2 gl, final int mode, final int count, final int type, final long indices_buffer_offset) {
+ validate(gl, true);
+ if ( GL2GL3.GL_QUADS == mode && !gl.isGL2() ) {
+ throw new GLException("Cannot handle indexed QUADS on !GL2 w/ VBO due to lack of CPU index access");
+ } else /* if( GL.GL_POINTS != mode ) */ {
+ gl.glDrawElements(mode, count, type, indices_buffer_offset);
+ } /* else {
+ // FIXME GL_POINTS !
+ gl.glDrawElements(mode, count, type, indices_buffer_offset);
+ } */
+ }
+
+ private final int textureEnabledCount() {
+ int n=0;
+ for(int i=MAX_TEXTURE_UNITS-1; i>=0; i--) {
+ if( 0 != textureEnabled.get(i) ) {
+ n++;
+ }
+ }
+ return n;
+ }
+
+ public void validate(final GL2ES2 gl, final boolean selectShader) {
+ if( selectShader ) {
+ if( ShaderSelectionMode.AUTO == requestedShaderSelectionMode) {
+ final ShaderSelectionMode newMode;
+
+ // pre-validate shader switch
+ if( 0 != textureEnabledBits ) {
+ if(lightingEnabled) {
+ newMode = ShaderSelectionMode.COLOR_TEXTURE8_LIGHT_PER_VERTEX;
+ } else {
+ final int n = textureEnabledCount();
+ if( 4 < n ) {
+ newMode = ShaderSelectionMode.COLOR_TEXTURE8;
+ } else if ( 2 < n ) {
+ newMode = ShaderSelectionMode.COLOR_TEXTURE4;
+ } else {
+ newMode = ShaderSelectionMode.COLOR_TEXTURE2;
+ }
+ }
+ } else {
+ if(lightingEnabled) {
+ newMode = ShaderSelectionMode.COLOR_LIGHT_PER_VERTEX;
+ } else {
+ newMode = ShaderSelectionMode.COLOR;
+ }
+ }
+ shaderState.attachShaderProgram(gl, selectShaderProgram(gl, newMode), true); // enables shader-program implicit
+ } else {
+ shaderState.useProgram(gl, true);
+ }
+ }
- public void validate(GL2ES2 gl) {
- shaderState.useProgram(gl, true);
GLUniformData ud;
- if(pmvMatrix.update()) {
+ if( pmvMatrix.update() ) {
ud = shaderState.getUniform(mgl_PMVMatrix);
if(null!=ud) {
+ final FloatBuffer m;
+ if(ShaderSelectionMode.COLOR_TEXTURE8_LIGHT_PER_VERTEX == currentShaderSelectionMode ||
+ ShaderSelectionMode.COLOR_LIGHT_PER_VERTEX== currentShaderSelectionMode ) {
+ m = pmvMatrix.glGetPMvMvitMatrixf();
+ } else {
+ m = pmvMatrix.glGetPMvMatrixf();
+ }
+ if(m != ud.getBuffer()) {
+ ud.setData(m);
+ }
// same data object ..
shaderState.uniform(gl, ud);
} else {
throw new GLException("Failed to update: mgl_PMVMatrix");
}
}
- ud = shaderState.getUniform(mgl_ColorEnabled);
- if(null!=ud) {
- int ca = (shaderState.isVertexAttribArrayEnabled(GLPointerFuncUtil.mgl_Color)==true)?1:0;
- if(ca!=ud.intValue()) {
- ud.setData(ca);
- shaderState.uniform(gl, ud);
+ if(colorVAEnabledDirty) {
+ ud = shaderState.getUniform(mgl_ColorEnabled);
+ if(null!=ud) {
+ final int ca = true == shaderState.isVertexAttribArrayEnabled(GLPointerFuncUtil.mgl_Color) ? 1 : 0 ;
+ if(ca!=ud.intValue()) {
+ ud.setData(ca);
+ shaderState.uniform(gl, ud);
+ }
+ } else {
+ throw new GLException("Failed to update: mgl_ColorEnabled");
}
+ colorVAEnabledDirty = false;
}
- ud = shaderState.getUniform(mgl_CullFace);
- if(null!=ud) {
- if(cullFace!=ud.intValue()) {
+ /** ES2 supports CullFace implicit
+ if(cullFaceDirty) {
+ ud = shaderState.getUniform(mgl_CullFace);
+ if(null!=ud) {
ud.setData(cullFace);
shaderState.uniform(gl, ud);
}
+ cullFaceDirty = false;
+ } */
+
+ if(alphaTestDirty) {
+ ud = shaderState.getUniform(mgl_AlphaTestFunc);
+ if(null!=ud) {
+ ud.setData(alphaTestFunc);
+ shaderState.uniform(gl, ud);
+ }
+ ud = shaderState.getUniform(mgl_AlphaTestRef);
+ if(null!=ud) {
+ ud.setData(alphaTestRef);
+ shaderState.uniform(gl, ud);
+ }
+ alphaTestDirty = false;
+ }
+ if(pointParamsDirty) {
+ ud = shaderState.getUniform(mgl_PointParams);
+ if(null!=ud) {
+ // same data object
+ shaderState.uniform(gl, ud);
+ }
+ pointParamsDirty = false;
}
if(lightsEnabledDirty) {
ud = shaderState.getUniform(mgl_LightsEnabled);
if(null!=ud) {
- // same data object
+ // same data object
shaderState.uniform(gl, ud);
}
lightsEnabledDirty=false;
}
- if(textureCoordsEnabledDirty) {
+ if(textureCoordEnabledDirty) {
ud = shaderState.getUniform(mgl_TexCoordEnabled);
if(null!=ud) {
- // same data object
+ // same data object
shaderState.uniform(gl, ud);
}
- textureCoordsEnabledDirty=false;
+ textureCoordEnabledDirty=false;
}
- if(textureEnabled) {
- if(lightingEnabled) {
- shaderState.attachShaderProgram(gl, shaderProgramColorTextureLight, true);
- } else {
- shaderState.attachShaderProgram(gl, shaderProgramColorTexture, true);
+ if(textureEnvModeDirty) {
+ ud = shaderState.getUniform(mgl_TexEnvMode);
+ if(null!=ud) {
+ // same data object
+ shaderState.uniform(gl, ud);
}
- } else {
- if(lightingEnabled) {
- shaderState.attachShaderProgram(gl, shaderProgramColorLight, true);
- } else {
- shaderState.attachShaderProgram(gl, shaderProgramColor, true);
+ textureEnvModeDirty = false;
+ }
+
+ if(textureFormatDirty) {
+ for(int i = 0; i<MAX_TEXTURE_UNITS; i++) {
+ textureFormat.put(i, texID2Format.get(boundTextureObject[i]));
}
+ ud = shaderState.getUniform(mgl_TexFormat);
+ if(null!=ud) {
+ // same data object
+ shaderState.uniform(gl, ud);
+ }
+ textureFormatDirty = false;
}
- if(DEBUG) {
- System.err.println("validate: "+this);
+ if(textureEnabledDirty) {
+ ud = shaderState.getUniform(mgl_TextureEnabled);
+ if(null!=ud) {
+ // same data object
+ shaderState.uniform(gl, ud);
+ }
+ textureEnabledDirty=false;
+ }
+
+ if(verbose) {
+ System.err.println("validate: "+toString(null, DEBUG).toString());
}
}
- public String toString() {
- return "FixedFuncPipeline[pmv: "+pmvMatrix+
- ", textureEnabled: "+textureEnabled+
- ", textureCoordsEnabled: "+textureCoordsEnabled+
- ", lightingEnabled: "+lightingEnabled+
- ", lightsEnabled: "+lightsEnabled+
- "\n\t, shaderProgramColor: "+shaderProgramColor+
- "\n\t, shaderProgramColorTexture: "+shaderProgramColorTexture+
- "\n\t, shaderProgramColorLight: "+shaderProgramColorLight+
- "\n\t, shaderProgramColorTextureLight: "+shaderProgramColorTextureLight+
- "\n\t, ShaderState: "+shaderState+
- "]";
- }
-
- protected void init(GL2ES2 gl, PMVMatrix pmvMatrix, Class shaderRootClass, String shaderSrcRoot, String shaderBinRoot,
- String vertexColorFile,
- String vertexColorLightFile,
- String fragmentColorFile,
- String fragmentColorTextureFile)
- {
- if(null==pmvMatrix) {
- throw new GLException("PMVMatrix is null");
+ public StringBuilder toString(StringBuilder sb, final boolean alsoUnlocated) {
+ if(null == sb) {
+ sb = new StringBuilder();
}
- this.pmvMatrix=pmvMatrix;
- this.shaderState=new ShaderState();
- this.shaderState.setVerbose(verbose);
- ShaderCode vertexColor, vertexColorLight, fragmentColor, fragmentColorTexture;
+ sb.append("FixedFuncPipeline[");
+ sb.append(", textureEnabled: "+toHexString(textureEnabledBits)+", "); Buffers.toString(sb, null, textureEnabled);
+ sb.append("\n\t, textureCoordEnabled: "); Buffers.toString(sb, null, textureCoordEnabled);
+ sb.append("\n\t lightingEnabled: "+lightingEnabled);
+ sb.append(", lightsEnabled: "); Buffers.toString(sb, null, lightsEnabled);
+ sb.append("\n\t, shaderProgramColor: "+shaderProgramColor);
+ sb.append("\n\t, shaderProgramColorTexture2: "+shaderProgramColorTexture2);
+ sb.append("\n\t, shaderProgramColorTexture4: "+shaderProgramColorTexture4);
+ sb.append("\n\t, shaderProgramColorTexture8: "+shaderProgramColorTexture8);
+ sb.append("\n\t, shaderProgramColorLight: "+shaderProgramColorLight);
+ sb.append("\n\t, shaderProgramColorTexture8Light: "+shaderProgramColorTexture8Light);
+ sb.append("\n\t, ShaderState: ");
+ shaderState.toString(sb, alsoUnlocated);
+ sb.append("]");
+ return sb;
+ }
+ @Override
+ public String toString() {
+ return toString(null, DEBUG).toString();
+ }
- vertexColor = ShaderCode.create( gl, gl.GL_VERTEX_SHADER, shaderRootClass, shaderSrcRoot,
- shaderBinRoot, vertexColorFile, false);
+ private static final String constMaxTextures0 = "#define MAX_TEXTURE_UNITS 0\n";
+ private static final String constMaxTextures2 = "#define MAX_TEXTURE_UNITS 2\n";
+ private static final String constMaxTextures4 = "#define MAX_TEXTURE_UNITS 4\n";
+ private static final String constMaxTextures8 = "#define MAX_TEXTURE_UNITS 8\n";
- vertexColorLight = ShaderCode.create( gl, gl.GL_VERTEX_SHADER, shaderRootClass, shaderSrcRoot,
- shaderBinRoot, vertexColorLightFile, false);
+ private final void customizeShader(final GL2ES2 gl, final ShaderCode vp, final ShaderCode fp, final String maxTextureDefine) {
+ final int rsVpPos = vp.defaultShaderCustomization(gl, true, true);
+ final int rsFpPos = fp.defaultShaderCustomization(gl, true, true);
+ vp.insertShaderSource(0, rsVpPos, maxTextureDefine);
+ fp.insertShaderSource(0, rsFpPos, maxTextureDefine);
+ }
- fragmentColor = ShaderCode.create( gl, gl.GL_FRAGMENT_SHADER, shaderRootClass, shaderSrcRoot,
- shaderBinRoot, fragmentColorFile, false);
+ private final void loadShaderPoints(final GL2ES2 gl) {
+ if( null != shaderProgramPoints ) {
+ return;
+ }
- fragmentColorTexture = ShaderCode.create( gl, gl.GL_FRAGMENT_SHADER, shaderRootClass, shaderSrcRoot,
- shaderBinRoot, fragmentColorTextureFile, false);
+ final ShaderCode vp = ShaderCode.create( gl, GL2ES2.GL_VERTEX_SHADER, shaderRootClass, shaderSrcRoot,
+ shaderBinRoot, shaderPointFileDef, true);
+ final ShaderCode fp = ShaderCode.create( gl, GL2ES2.GL_FRAGMENT_SHADER, shaderRootClass, shaderSrcRoot,
+ shaderBinRoot, shaderPointFileDef, true);
+ customizeShader(gl, vp, fp, constMaxTextures2);
+ shaderProgramPoints = new ShaderProgram();
+ shaderProgramPoints.add(vp);
+ shaderProgramPoints.add(fp);
+ if(!shaderProgramPoints.link(gl, System.err)) {
+ throw new GLException("Couldn't link VertexColor program: "+shaderProgramPoints);
+ }
+ }
+
+ private final void loadShader(final GL2ES2 gl, final ShaderSelectionMode mode) {
+ final boolean loadColor = ShaderSelectionMode.COLOR == mode;
+ final boolean loadColorTexture2 = ShaderSelectionMode.COLOR_TEXTURE2 == mode;
+ final boolean loadColorTexture4 = ShaderSelectionMode.COLOR_TEXTURE4 == mode;
+ final boolean loadColorTexture8 = ShaderSelectionMode.COLOR_TEXTURE8 == mode;
+ final boolean loadColorTexture = loadColorTexture2 || loadColorTexture4 || loadColorTexture8 ;
+ final boolean loadColorLightPerVertex = ShaderSelectionMode.COLOR_LIGHT_PER_VERTEX == mode;
+ final boolean loadColorTexture8LightPerVertex = ShaderSelectionMode.COLOR_TEXTURE8_LIGHT_PER_VERTEX == mode;
- shaderProgramColor = new ShaderProgram();
- shaderProgramColor.add(vertexColor);
- shaderProgramColor.add(fragmentColor);
- if(!shaderProgramColor.link(gl, System.err)) {
- throw new GLException("Couldn't link VertexColor program: "+shaderProgramColor);
+ if( null != shaderProgramColor && loadColor ||
+ null != shaderProgramColorTexture2 && loadColorTexture2 ||
+ null != shaderProgramColorTexture4 && loadColorTexture4 ||
+ null != shaderProgramColorTexture8 && loadColorTexture8 ||
+ null != shaderProgramColorLight && loadColorLightPerVertex ||
+ null != shaderProgramColorTexture8Light && loadColorTexture8LightPerVertex ) {
+ return;
}
- shaderProgramColorTexture = new ShaderProgram();
- shaderProgramColorTexture.add(vertexColor);
- shaderProgramColorTexture.add(fragmentColorTexture);
- if(!shaderProgramColorTexture.link(gl, System.err)) {
- throw new GLException("Couldn't link VertexColorTexture program: "+shaderProgramColorTexture);
+ if( loadColor ) {
+ final ShaderCode vp = ShaderCode.create( gl, GL2ES2.GL_VERTEX_SHADER, shaderRootClass, shaderSrcRoot,
+ shaderBinRoot, vertexColorFile, true);
+ final ShaderCode fp = ShaderCode.create( gl, GL2ES2.GL_FRAGMENT_SHADER, shaderRootClass, shaderSrcRoot,
+ shaderBinRoot, fragmentColorFile, true);
+ customizeShader(gl, vp, fp, constMaxTextures0);
+ shaderProgramColor = new ShaderProgram();
+ shaderProgramColor.add(vp);
+ shaderProgramColor.add(fp);
+ if(!shaderProgramColor.link(gl, System.err)) {
+ throw new GLException("Couldn't link VertexColor program: "+shaderProgramColor);
+ }
+ } else if( loadColorTexture ) {
+ final ShaderCode vp = ShaderCode.create( gl, GL2ES2.GL_VERTEX_SHADER, shaderRootClass, shaderSrcRoot, shaderBinRoot, vertexColorFile, true);
+ final ShaderCode fp = ShaderCode.create( gl, GL2ES2.GL_FRAGMENT_SHADER, shaderRootClass, shaderSrcRoot,
+ shaderBinRoot, fragmentColorTextureFile, true);
+
+ if( loadColorTexture2 ) {
+ customizeShader(gl, vp, fp, constMaxTextures2);
+ shaderProgramColorTexture2 = new ShaderProgram();
+ shaderProgramColorTexture2.add(vp);
+ shaderProgramColorTexture2.add(fp);
+ if(!shaderProgramColorTexture2.link(gl, System.err)) {
+ throw new GLException("Couldn't link VertexColorTexture2 program: "+shaderProgramColorTexture2);
+ }
+ } else if( loadColorTexture4 ) {
+ customizeShader(gl, vp, fp, constMaxTextures4);
+ shaderProgramColorTexture4 = new ShaderProgram();
+ shaderProgramColorTexture4.add(vp);
+ shaderProgramColorTexture4.add(fp);
+ if(!shaderProgramColorTexture4.link(gl, System.err)) {
+ throw new GLException("Couldn't link VertexColorTexture4 program: "+shaderProgramColorTexture4);
+ }
+ } else if( loadColorTexture8 ) {
+ customizeShader(gl, vp, fp, constMaxTextures8);
+ shaderProgramColorTexture8 = new ShaderProgram();
+ shaderProgramColorTexture8.add(vp);
+ shaderProgramColorTexture8.add(fp);
+ if(!shaderProgramColorTexture8.link(gl, System.err)) {
+ throw new GLException("Couldn't link VertexColorTexture8 program: "+shaderProgramColorTexture8);
+ }
+ }
+ } else if( loadColorLightPerVertex ) {
+ final ShaderCode vp = ShaderCode.create( gl, GL2ES2.GL_VERTEX_SHADER, shaderRootClass, shaderSrcRoot,
+ shaderBinRoot, vertexColorLightFile, true);
+ final ShaderCode fp = ShaderCode.create( gl, GL2ES2.GL_FRAGMENT_SHADER, shaderRootClass, shaderSrcRoot,
+ shaderBinRoot, fragmentColorFile, true);
+ customizeShader(gl, vp, fp, constMaxTextures0);
+ shaderProgramColorLight = new ShaderProgram();
+ shaderProgramColorLight.add(vp);
+ shaderProgramColorLight.add(fp);
+ if(!shaderProgramColorLight.link(gl, System.err)) {
+ throw new GLException("Couldn't link VertexColorLight program: "+shaderProgramColorLight);
+ }
+ } else if( loadColorTexture8LightPerVertex ) {
+ final ShaderCode vp = ShaderCode.create( gl, GL2ES2.GL_VERTEX_SHADER, shaderRootClass, shaderSrcRoot,
+ shaderBinRoot, vertexColorLightFile, true);
+ final ShaderCode fp = ShaderCode.create( gl, GL2ES2.GL_FRAGMENT_SHADER, shaderRootClass, shaderSrcRoot,
+ shaderBinRoot, fragmentColorTextureFile, true);
+ customizeShader(gl, vp, fp, constMaxTextures8);
+ shaderProgramColorTexture8Light = new ShaderProgram();
+ shaderProgramColorTexture8Light.add(vp);
+ shaderProgramColorTexture8Light.add(fp);
+ if(!shaderProgramColorTexture8Light.link(gl, System.err)) {
+ throw new GLException("Couldn't link VertexColorLight program: "+shaderProgramColorTexture8Light);
+ }
}
+ }
- shaderProgramColorLight = new ShaderProgram();
- shaderProgramColorLight.add(vertexColorLight);
- shaderProgramColorLight.add(fragmentColor);
- if(!shaderProgramColorLight.link(gl, System.err)) {
- throw new GLException("Couldn't link VertexColorLight program: "+shaderProgramColorLight);
+ private ShaderProgram selectShaderProgram(final GL2ES2 gl, ShaderSelectionMode newMode) {
+ if(ShaderSelectionMode.AUTO == newMode) {
+ newMode = ShaderSelectionMode.COLOR;
}
+ loadShader(gl, newMode);
+ final ShaderProgram sp;
+ switch(newMode) {
+ case COLOR_LIGHT_PER_VERTEX:
+ sp = shaderProgramColorLight;
+ break;
+ case COLOR_TEXTURE2:
+ sp = shaderProgramColorTexture2;
+ break;
+ case COLOR_TEXTURE4:
+ sp = shaderProgramColorTexture4;
+ break;
+ case COLOR_TEXTURE8:
+ sp = shaderProgramColorTexture8;
+ break;
+ case COLOR_TEXTURE8_LIGHT_PER_VERTEX:
+ sp = shaderProgramColorTexture8Light;
+ break;
+ case COLOR:
+ default:
+ sp = shaderProgramColor;
+ }
+ currentShaderSelectionMode = newMode;
+ return sp;
+ }
- shaderProgramColorTextureLight = new ShaderProgram();
- shaderProgramColorTextureLight.add(vertexColorLight);
- shaderProgramColorTextureLight.add(fragmentColorTexture);
- if(!shaderProgramColorTextureLight.link(gl, System.err)) {
- throw new GLException("Couldn't link VertexColorLight program: "+shaderProgramColorTextureLight);
+ private void init(final GL2ES2 gl, final ShaderSelectionMode mode, final PMVMatrix pmvMatrix) {
+ if(null==pmvMatrix) {
+ throw new GLException("PMVMatrix is null");
}
+ this.pmvMatrix=pmvMatrix;
+ this.requestedShaderSelectionMode = mode;
+ this.shaderState=new ShaderState();
+ this.shaderState.setVerbose(verbose);
- shaderState.attachShaderProgram(gl, shaderProgramColor, true);
+ shaderState.attachShaderProgram(gl, selectShaderProgram(gl, requestedShaderSelectionMode), true);
// mandatory ..
if(!shaderState.uniform(gl, new GLUniformData(mgl_PMVMatrix, 4, 4, pmvMatrix.glGetPMvMvitMatrixf()))) {
@@ -457,16 +1116,26 @@ public class FixedFuncPipeline {
}
shaderState.uniform(gl, new GLUniformData(mgl_ColorEnabled, 0));
- shaderState.uniform(gl, new GLUniformData(mgl_ColorStatic, 4, zero4f));
- shaderState.uniform(gl, new GLUniformData(mgl_TexCoordEnabled, 1, textureCoordsEnabled));
- shaderState.uniform(gl, new GLUniformData(mgl_ActiveTexture, activeTextureUnit));
- shaderState.uniform(gl, new GLUniformData(mgl_ActiveTextureIdx, activeTextureUnit));
+ shaderState.uniform(gl, new GLUniformData(mgl_ColorStatic, 4, colorStatic));
+
+ texID2Format.setKeyNotFoundValue(0);
+ shaderState.uniform(gl, new GLUniformData(mgl_TexCoordEnabled, 1, textureCoordEnabled));
+ shaderState.uniform(gl, new GLUniformData(mgl_TexEnvMode, 1, textureEnvMode));
+ shaderState.uniform(gl, new GLUniformData(mgl_TexFormat, 1, textureFormat));
+ shaderState.uniform(gl, new GLUniformData(mgl_TextureEnabled, 1, textureEnabled));
+ for(int i=0; i<MAX_TEXTURE_UNITS; i++) {
+ shaderState.uniform(gl, new GLUniformData(mgl_Texture+i, i));
+ }
shaderState.uniform(gl, new GLUniformData(mgl_ShadeModel, 0));
- shaderState.uniform(gl, new GLUniformData(mgl_CullFace, cullFace));
+ /** ES2 supports CullFace implicit
+ shaderState.uniform(gl, new GLUniformData(mgl_CullFace, cullFace)); */
+ shaderState.uniform(gl, new GLUniformData(mgl_AlphaTestFunc, alphaTestFunc));
+ shaderState.uniform(gl, new GLUniformData(mgl_AlphaTestRef, alphaTestRef));
+ shaderState.uniform(gl, new GLUniformData(mgl_PointParams, 4, pointParams));
for(int i=0; i<MAX_LIGHTS; i++) {
shaderState.uniform(gl, new GLUniformData(mgl_LightSource+"["+i+"].ambient", 4, defAmbient));
- shaderState.uniform(gl, new GLUniformData(mgl_LightSource+"["+i+"].diffuse", 4, defDiffuse));
- shaderState.uniform(gl, new GLUniformData(mgl_LightSource+"["+i+"].specular", 4, defSpecular));
+ shaderState.uniform(gl, new GLUniformData(mgl_LightSource+"["+i+"].diffuse", 4, 0==i ? one4f : defDiffuseN));
+ shaderState.uniform(gl, new GLUniformData(mgl_LightSource+"["+i+"].specular", 4, 0==i ? one4f : defSpecularN));
shaderState.uniform(gl, new GLUniformData(mgl_LightSource+"["+i+"].position", 4, defPosition));
shaderState.uniform(gl, new GLUniformData(mgl_LightSource+"["+i+"].spotDirection", 3, defSpotDir));
shaderState.uniform(gl, new GLUniformData(mgl_LightSource+"["+i+"].spotExponent", defSpotExponent));
@@ -475,6 +1144,7 @@ public class FixedFuncPipeline {
shaderState.uniform(gl, new GLUniformData(mgl_LightSource+"["+i+"].linearAttenuation", defLinearAtten));
shaderState.uniform(gl, new GLUniformData(mgl_LightSource+"["+i+"].quadraticAttenuation", defQuadraticAtten));
}
+ shaderState.uniform(gl, new GLUniformData(mgl_LightModel+".ambient", 4, defLightModelAmbient));
shaderState.uniform(gl, new GLUniformData(mgl_LightsEnabled, 1, lightsEnabled));
shaderState.uniform(gl, new GLUniformData(mgl_FrontMaterial+".ambient", 4, defMatAmbient));
shaderState.uniform(gl, new GLUniformData(mgl_FrontMaterial+".diffuse", 4, defMatDiffuse));
@@ -483,70 +1153,121 @@ public class FixedFuncPipeline {
shaderState.uniform(gl, new GLUniformData(mgl_FrontMaterial+".shininess", defMatShininess));
shaderState.useProgram(gl, false);
+ if(verbose) {
+ System.err.println("init: "+toString(null, DEBUG).toString());
+ }
}
- protected static final boolean DEBUG=false;
- protected boolean verbose=false;
+ private String toHexString(final int i) {
+ return "0x"+Integer.toHexString(i);
+ }
+
+ protected boolean verbose = DEBUG;
+
+ private final FloatBuffer colorStatic = Buffers.copyFloatBuffer(one4f);
+
+ private int activeTextureUnit=0;
+ private int clientActiveTextureUnit=0;
+ private final IntIntHashMap texID2Format = new IntIntHashMap();
+ private final int[] boundTextureObject = new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }; // per unit
+ private int textureEnabledBits = 0;
+ private final IntBuffer textureEnabled = Buffers.newDirectIntBuffer(new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }); // per unit
+ private boolean textureEnabledDirty = false;
+ private final IntBuffer textureCoordEnabled = Buffers.newDirectIntBuffer(new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }); // per unit
+ private boolean textureCoordEnabledDirty = false;
+ // textureEnvMode: 1 GL_ADD, 2 GL_MODULATE (default), 3 GL_DECAL, 4 GL_BLEND, 5 GL_REPLACE, 6 GL_COMBINE
+ private final IntBuffer textureEnvMode = Buffers.newDirectIntBuffer(new int[] { 2, 2, 2, 2, 2, 2, 2, 2 });
+ private boolean textureEnvModeDirty = false;
+ private final IntBuffer textureFormat = Buffers.newDirectIntBuffer(new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }); // per unit
+ private boolean textureFormatDirty = false;
+
+ /** ES2 supports CullFace implicit
+ private int cullFace=-2; // <=0 disabled, 1 GL_FRONT, 2 GL_BACK (default) and 3 GL_FRONT_AND_BACK
+ private boolean cullFaceDirty = false;
+ private static final String mgl_CullFace = "mgl_CullFace"; // 1i (lowp int) */
- protected boolean textureEnabled=false;
- protected IntBuffer textureCoordsEnabled = Buffers.newDirectIntBuffer(new int[] { 0, 0, 0, 0, 0, 0, 0, 0 });
- protected boolean textureCoordsEnabledDirty = false;
- protected int activeTextureUnit=0;
+ private boolean colorVAEnabledDirty = false;
+ private boolean lightingEnabled=false;
+ private final IntBuffer lightsEnabled = Buffers.newDirectIntBuffer(new int[] { 0, 0, 0, 0, 0, 0, 0, 0 });
+ private boolean lightsEnabledDirty = false;
- protected int cullFace=-2; // <=0 disabled, 1: front, 2: back (default, but disabled), 3: front & back
+ private boolean alphaTestDirty=false;
+ private int alphaTestFunc=-8; // <=0 disabled; 1 GL_NEVER, 2 GL_LESS, 3 GL_EQUAL, 4 GL_LEQUAL, 5 GL_GREATER, 6 GL_NOTEQUAL, 7 GL_GEQUAL, and 8 GL_ALWAYS (default)
+ private float alphaTestRef=0f;
- protected boolean lightingEnabled=false;
- protected IntBuffer lightsEnabled = Buffers.newDirectIntBuffer(new int[] { 0, 0, 0, 0, 0, 0, 0, 0 });
- protected boolean lightsEnabledDirty = false;
+ private boolean pointParamsDirty = false;
+ /** ( pointSize, pointSmooth, attn. pointMinSize, attn. pointMaxSize ) , ( attenuation coefficients 1f 0f 0f, attenuation fade theshold 1f ) */
+ private final FloatBuffer pointParams = Buffers.newDirectFloatBuffer(new float[] { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f });
- protected PMVMatrix pmvMatrix;
- protected ShaderState shaderState;
- protected ShaderProgram shaderProgramColor;
- protected ShaderProgram shaderProgramColorTexture;
- protected ShaderProgram shaderProgramColorLight;
- protected ShaderProgram shaderProgramColorTextureLight;
+ private PMVMatrix pmvMatrix;
+ private ShaderState shaderState;
+ private ShaderProgram shaderProgramColor;
+ private ShaderProgram shaderProgramColorTexture2, shaderProgramColorTexture4, shaderProgramColorTexture8;
+ private ShaderProgram shaderProgramColorLight;
+ private ShaderProgram shaderProgramColorTexture8Light;
+ private ShaderProgram shaderProgramPoints;
+
+ private ShaderSelectionMode requestedShaderSelectionMode = ShaderSelectionMode.AUTO;
+ private ShaderSelectionMode currentShaderSelectionMode = requestedShaderSelectionMode;
// uniforms ..
- protected static final String mgl_PMVMatrix = "mgl_PMVMatrix"; // m4fv[4] - P, Mv, Mvi and Mvit
- protected static final String mgl_ColorEnabled = "mgl_ColorEnabled"; // 1i
- protected static final String mgl_ColorStatic = "mgl_ColorStatic"; // 4fv
-
- protected static final String mgl_LightSource = "mgl_LightSource"; // struct mgl_LightSourceParameters[MAX_LIGHTS]
- protected static final String mgl_FrontMaterial = "mgl_FrontMaterial"; // struct mgl_MaterialParameters
- protected static final String mgl_LightsEnabled = "mgl_LightsEnabled"; // int mgl_LightsEnabled[MAX_LIGHTS];
-
- protected static final String mgl_ShadeModel = "mgl_ShadeModel"; // 1i
-
- protected static final String mgl_TexCoordEnabled = "mgl_TexCoordEnabled"; // int mgl_TexCoordEnabled[MAX_TEXTURE_UNITS];
- protected static final String mgl_ActiveTexture = "mgl_ActiveTexture"; // 1i
- protected static final String mgl_ActiveTextureIdx = "mgl_ActiveTextureIdx";// 1i
-
- protected static final String mgl_CullFace = "mgl_CullFace"; // 1i
-
- protected static final FloatBuffer zero4f = Buffers.newDirectFloatBuffer(new float[] { 0.0f, 0.0f, 0.0f, 0.0f });
-
- public static final FloatBuffer defAmbient = Buffers.newDirectFloatBuffer(new float[] { 0f, 0f, 0f, 1f });
- public static final FloatBuffer defDiffuse = zero4f;
- public static final FloatBuffer defSpecular= zero4f;
- public static final FloatBuffer defPosition= Buffers.newDirectFloatBuffer(new float[] { 0f, 0f, 1f, 0f });
- public static final FloatBuffer defSpotDir = Buffers.newDirectFloatBuffer(new float[] { 0f, 0f, -1f });
- public static final float defSpotExponent = 0f;
- public static final float defSpotCutoff = 180f;
- public static final float defConstantAtten = 1f;
- public static final float defLinearAtten = 0f;
- public static final float defQuadraticAtten= 0f;
-
- public static final FloatBuffer defMatAmbient = Buffers.newDirectFloatBuffer(new float[] { 0.2f, 0.2f, 0.2f, 1.0f });
- public static final FloatBuffer defMatDiffuse = Buffers.newDirectFloatBuffer(new float[] { 0.8f, 0.8f, 0.8f, 1.0f });
- public static final FloatBuffer defMatSpecular= Buffers.newDirectFloatBuffer(new float[] { 0f, 0f, 0f, 1f});
- public static final FloatBuffer defMatEmission= Buffers.newDirectFloatBuffer(new float[] { 0f, 0f, 0f, 1f});
+ private static final String mgl_PMVMatrix = "mgl_PMVMatrix"; // m4fv[4] - P, Mv, Mvi and Mvit
+ private static final String mgl_ColorEnabled = "mgl_ColorEnabled"; // 1i
+ private static final String mgl_ColorStatic = "mgl_ColorStatic"; // 4fv
+
+ private static final String mgl_LightModel = "mgl_LightModel"; // struct mgl_LightModelParameters
+ private static final String mgl_LightSource = "mgl_LightSource"; // struct mgl_LightSourceParameters[MAX_LIGHTS]
+ private static final String mgl_FrontMaterial = "mgl_FrontMaterial"; // struct mgl_MaterialParameters
+ private static final String mgl_LightsEnabled = "mgl_LightsEnabled"; // int mgl_LightsEnabled[MAX_LIGHTS];
+
+ private static final String mgl_AlphaTestFunc = "mgl_AlphaTestFunc"; // 1i (lowp int)
+ private static final String mgl_AlphaTestRef = "mgl_AlphaTestRef"; // 1f
+ private static final String mgl_ShadeModel = "mgl_ShadeModel"; // 1i
+ private static final String mgl_PointParams = "mgl_PointParams"; // vec4[2]: { (sz, smooth, attnMinSz, attnMaxSz), (attnCoeff(3), attnFadeTs) }
+
+ private static final String mgl_TextureEnabled = "mgl_TextureEnabled"; // int mgl_TextureEnabled[MAX_TEXTURE_UNITS];
+ private static final String mgl_Texture = "mgl_Texture"; // sampler2D mgl_Texture<0..7>
+ private static final String mgl_TexCoordEnabled = "mgl_TexCoordEnabled"; // int mgl_TexCoordEnabled[MAX_TEXTURE_UNITS];
+ private static final String mgl_TexEnvMode = "mgl_TexEnvMode"; // int mgl_TexEnvMode[MAX_TEXTURE_UNITS];
+ private static final String mgl_TexFormat = "mgl_TexFormat"; // int mgl_TexFormat[MAX_TEXTURE_UNITS];
+
+ // private static final FloatBuffer zero4f = Buffers.newDirectFloatBuffer(new float[] { 0.0f, 0.0f, 0.0f, 0.0f });
+ private static final FloatBuffer neut4f = Buffers.newDirectFloatBuffer(new float[] { 0.0f, 0.0f, 0.0f, 1.0f });
+ private static final FloatBuffer one4f = Buffers.newDirectFloatBuffer(new float[] { 1.0f, 1.0f, 1.0f, 1.0f });
+
+ public static final FloatBuffer defAmbient = neut4f;
+ public static final FloatBuffer defDiffuseN = neut4f;
+ public static final FloatBuffer defSpecularN = neut4f;
+ public static final FloatBuffer defPosition = Buffers.newDirectFloatBuffer(new float[] { 0f, 0f, 1f, 0f });
+ public static final FloatBuffer defSpotDir = Buffers.newDirectFloatBuffer(new float[] { 0f, 0f, -1f });
+ public static final float defSpotExponent = 0f;
+ public static final float defSpotCutoff = 180f;
+ public static final float defConstantAtten = 1f;
+ public static final float defLinearAtten = 0f;
+ public static final float defQuadraticAtten = 0f;
+
+ public static final FloatBuffer defLightModelAmbient = Buffers.newDirectFloatBuffer(new float[] { 0.2f, 0.2f, 0.2f, 1.0f });
+
+ public static final FloatBuffer defMatAmbient = Buffers.newDirectFloatBuffer(new float[] { 0.2f, 0.2f, 0.2f, 1.0f });
+ public static final FloatBuffer defMatDiffuse = Buffers.newDirectFloatBuffer(new float[] { 0.8f, 0.8f, 0.8f, 1.0f });
+ public static final FloatBuffer defMatSpecular = neut4f;
+ public static final FloatBuffer defMatEmission = neut4f;
public static final float defMatShininess = 0f;
- protected static final String vertexColorFileDef = "FixedFuncColor";
- protected static final String vertexColorLightFileDef = "FixedFuncColorLight";
- protected static final String fragmentColorFileDef = "FixedFuncColor";
- protected static final String fragmentColorTextureFileDef = "FixedFuncColorTexture";
- protected static final String shaderSrcRootDef = "shaders" ;
- protected static final String shaderBinRootDef = "shaders/bin" ;
+ private static final String vertexColorFileDef = "FixedFuncColor";
+ private static final String vertexColorLightFileDef = "FixedFuncColorLight";
+ private static final String fragmentColorFileDef = "FixedFuncColor";
+ private static final String fragmentColorTextureFileDef = "FixedFuncColorTexture";
+ private static final String shaderPointFileDef = "FixedFuncPoints";
+ private static final String shaderSrcRootDef = "shaders" ;
+ private static final String shaderBinRootDef = "shaders/bin" ;
+
+ private final Class<?> shaderRootClass;
+ private final String shaderSrcRoot;
+ private final String shaderBinRoot;
+ private final String vertexColorFile;
+ private final String vertexColorLightFile;
+ private final String fragmentColorFile;
+ private final String fragmentColorTextureFile;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.fp b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.fp
index 408ff7251..22dd1e61a 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.fp
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.fp
@@ -1,16 +1,32 @@
+
+#if __VERSION__ >= 130
+ #define varying in
+ out vec4 mgl_FragColor;
+#else
+ #define mgl_FragColor gl_FragColor
+#endif
+
#include es_precision.glsl
#include mgl_uniform.glsl
#include mgl_varying.glsl
+#include mgl_alphatest.fp
+
void main (void)
{
- if( mgl_CullFace > 0 &&
- ( ( mgl_CullFace == 1 && gl_FrontFacing ) ||
- ( mgl_CullFace == 2 && !gl_FrontFacing ) ||
- ( mgl_CullFace == 3 ) ) ) {
- discard;
+ vec4 color = frontColor;
+
+ /** ES2 supports CullFace implicit ..
+ if( mgl_CullFace > 0 &&
+ ( ( MGL_FRONT == mgl_CullFace && gl_FrontFacing ) ||
+ ( MGL_BACK == mgl_CullFace && !gl_FrontFacing ) ||
+ ( MGL_FRONT_AND_BACK == mgl_CullFace ) ) ) {
+ DISCARD(color);
+ } */
+ if( mgl_AlphaTestFunc > 0 ) {
+ alphaTest(color);
}
- gl_FragColor = frontColor;
+ mgl_FragColor = color;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.vp b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.vp
index 346e40196..f39fcfbd0 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.vp
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.vp
@@ -1,3 +1,9 @@
+
+#if __VERSION__ >= 130
+ #define attribute in
+ #define varying out
+#endif
+
#include es_precision.glsl
#include mgl_const.glsl
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorLight.vp b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorLight.vp
index 7ce1eedcf..942a540af 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorLight.vp
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorLight.vp
@@ -1,3 +1,9 @@
+
+#if __VERSION__ >= 130
+ #define attribute in
+ #define varying out
+#endif
+
#include es_precision.glsl
#include mgl_lightdef.glsl
@@ -50,16 +56,18 @@ void main(void)
}
}
}
- ambient *= mgl_FrontMaterial.ambient;
- diffuse *= mgl_FrontMaterial.diffuse;
- specular *= mgl_FrontMaterial.specular;
-
if(mgl_ColorEnabled>0) {
frontColor=mgl_Color;
} else {
frontColor=mgl_ColorStatic;
}
if( lightEnabled ) {
+ // light-ambient + global-ambient
+ // ( mgl_LightSource[0..n].ambient * mgl_FrontMaterial.ambient ) + ( mgl_LightModel.ambient * mgl_FrontMaterial.ambient )
+ ambient = ( ambient + mgl_LightModel.ambient ) * mgl_FrontMaterial.ambient;
+ diffuse *= mgl_FrontMaterial.diffuse;
+ specular *= mgl_FrontMaterial.specular;
+
frontColor *= ambient + diffuse + specular;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorTexture.fp b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorTexture.fp
index 86e6ace73..130711e19 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorTexture.fp
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorTexture.fp
@@ -1,4 +1,13 @@
+#if __VERSION__ >= 130
+ #define varying in
+ out vec4 mgl_FragColor;
+ #define texture2D texture
+#else
+ #define mgl_FragColor gl_FragColor
+#endif
+
+
#include es_precision.glsl
#include mgl_lightdef.glsl
@@ -6,42 +15,103 @@
#include mgl_uniform.glsl
#include mgl_varying.glsl
-vec4 getTexColor(in sampler2D tex, in int idx) {
- vec4 coord;
- if(idx==0) {
- coord= mgl_TexCoords[0];
- } else if(idx==1) {
- coord= mgl_TexCoords[1];
- } else if(idx==2) {
- coord= mgl_TexCoords[2];
- } else if(idx==3) {
- coord= mgl_TexCoords[3];
- } else if(idx==4) {
- coord= mgl_TexCoords[4];
- } else if(idx==5) {
- coord= mgl_TexCoords[5];
- } else if(idx==6) {
- coord= mgl_TexCoords[6];
- } else {
- coord= mgl_TexCoords[7];
+#include mgl_alphatest.fp
+
+const float gamma = 1.5; // FIXME
+const vec3 igammav = vec3(1.0 / gamma); // FIXME
+const vec4 texEnvColor = vec4(0.0); // FIXME
+
+const vec4 zerov4 = vec4(0.0);
+const vec4 onev4 = vec4(1.0);
+
+void calcTexColor(inout vec4 color, vec4 texColor, in int texFormat, in int texEnvMode) {
+ if(MGL_MODULATE == texEnvMode) { // default
+ if( 4 == texFormat ) {
+ color *= texColor;
+ } else {
+ color.rgb *= texColor.rgb;
+ }
+ } else if(MGL_REPLACE == texEnvMode) {
+ if( 4 == texFormat ) {
+ color = texColor;
+ } else {
+ color.rgb = texColor.rgb;
+ }
+ } else if(MGL_ADD == texEnvMode) {
+ if( 4 == texFormat ) {
+ color += texColor;
+ } else {
+ color.rgb += texColor.rgb;
+ }
+ } else if(MGL_BLEND == texEnvMode) {
+ color.rgb = mix(color.rgb, texEnvColor.rgb, texColor.rgb);
+ if( 4 == texFormat ) {
+ color.a *= texColor.a;
+ }
+ } else if(MGL_DECAL == texEnvMode) {
+ if( 4 == texFormat ) {
+ color.rgb = mix(color.rgb, texColor.rgb, texColor.a);
+ } else {
+ color.rgb = texColor.rgb;
+ }
}
- return texture2D(tex, coord.st);
+ color = clamp(color, zerov4, onev4);
}
void main (void)
-{
- if( mgl_CullFace > 0 &&
- ( ( mgl_CullFace == 1 && gl_FrontFacing ) ||
- ( mgl_CullFace == 2 && !gl_FrontFacing ) ||
- ( mgl_CullFace == 3 ) ) ) {
- discard;
- }
-
- vec4 texColor = getTexColor(mgl_ActiveTexture,mgl_ActiveTextureIdx);
-
- if(length(texColor.rgb)>0.0) {
- gl_FragColor = vec4(frontColor.rgb*texColor.rgb, frontColor.a) ;
+{
+ vec4 color = frontColor;
+
+ /** ES2 supports CullFace implicit ..
+ if( mgl_CullFace > 0 &&
+ ( ( MGL_FRONT == mgl_CullFace && gl_FrontFacing ) ||
+ ( MGL_BACK == mgl_CullFace && !gl_FrontFacing ) ||
+ ( MGL_FRONT_AND_BACK == mgl_CullFace ) ) ) {
+ DISCARD(color);
+ } else { */
+ #if MAX_TEXTURE_UNITS >= 2
+ if( 0 != mgl_TextureEnabled[0] ) {
+ calcTexColor(color, texture2D(mgl_Texture0, mgl_TexCoords[0].st), mgl_TexFormat[0], mgl_TexEnvMode[0]);
+ }
+ if( 0 != mgl_TextureEnabled[1] ) {
+ calcTexColor(color, texture2D(mgl_Texture1, mgl_TexCoords[1].st), mgl_TexFormat[1], mgl_TexEnvMode[1]);
+ }
+ #endif
+ #if MAX_TEXTURE_UNITS >= 4
+ if( 0 != mgl_TextureEnabled[2] ) {
+ calcTexColor(color, texture2D(mgl_Texture2, mgl_TexCoords[2].st), mgl_TexFormat[2], mgl_TexEnvMode[2]);
+ }
+ if( 0 != mgl_TextureEnabled[3] ) {
+ calcTexColor(color, texture2D(mgl_Texture3, mgl_TexCoords[3].st), mgl_TexFormat[3], mgl_TexEnvMode[3]);
+ }
+ #endif
+ #if MAX_TEXTURE_UNITS >= 8
+ if( 0 != mgl_TextureEnabled[4] ) {
+ calcTexColor(color, texture2D(mgl_Texture4, mgl_TexCoords[4].st), mgl_TexFormat[4], mgl_TexEnvMode[4]);
+ }
+ if( 0 != mgl_TextureEnabled[5] ) {
+ calcTexColor(color, texture2D(mgl_Texture5, mgl_TexCoords[5].st), mgl_TexFormat[5], mgl_TexEnvMode[5]);
+ }
+ if( 0 != mgl_TextureEnabled[6] ) {
+ calcTexColor(color, texture2D(mgl_Texture6, mgl_TexCoords[6].st), mgl_TexFormat[6], mgl_TexEnvMode[6]);
+ }
+ if( 0 != mgl_TextureEnabled[7] ) {
+ calcTexColor(color, texture2D(mgl_Texture7, mgl_TexCoords[7].st), mgl_TexFormat[7], mgl_TexEnvMode[7]);
+ }
+ #endif
+ if( mgl_AlphaTestFunc > 0 ) {
+ alphaTest(color);
+ }
+ // } /* CullFace */
+
+ mgl_FragColor = color;
+ /**
+ // simple alpha check
+ if (color.a != 0.0) {
+ mgl_FragColor = vec4(pow(color.rgb, igammav), color.a);
} else {
- gl_FragColor = frontColor;
- }
+ // discard; // freezes NV tegra2 compiler
+ mgl_FragColor = color;
+ } */
}
+
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncPoints.fp b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncPoints.fp
new file mode 100644
index 000000000..2d58f2320
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncPoints.fp
@@ -0,0 +1,47 @@
+
+#if __VERSION__ >= 130
+ #define varying in
+ out vec4 mgl_FragColor;
+#else
+ #define mgl_FragColor gl_FragColor
+#endif
+
+
+#include es_precision.glsl
+#include mgl_lightdef.glsl
+
+#include mgl_const.glsl
+#include mgl_uniform.glsl
+#include mgl_varying.glsl
+
+// #define TEST 1
+
+void main (void)
+{
+ mgl_FragColor = frontColor;
+
+ if( pointSmooth > 0.5 ) {
+ // smooth (AA)
+ const float border = 0.90; // take/give 10% for AA
+
+ // origin to 0/0, [-1/-1 .. 1/1]
+ vec2 pointPos = 2.0 * gl_PointCoord - 1.0 ;
+ float r = length( pointPos ); // one-circle sqrt(x * x + y * y), range: in-circle [0..1], out >1
+ float r1 = 1.0 - ( step(border, r) * 10.0 * ( r - border ) ) ; // [0..1]
+ #ifndef TEST
+ if( r1 < 0.0 ) {
+ discard;
+ }
+ #endif
+
+ #ifndef TEST
+ mgl_FragColor.a *= r1;
+ #else
+ mgl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
+ mgl_FragColor.r = r1 < 0.0 ? 1.0 : 0.0;
+ mgl_FragColor.g = r > 1.0 ? 1.0 : 0.0;
+ mgl_FragColor.b = r > border ? 1.0 : 0.0;
+ #endif
+ }
+}
+
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncPoints.vp b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncPoints.vp
new file mode 100644
index 000000000..4a5d93a3d
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncPoints.vp
@@ -0,0 +1,40 @@
+
+#if __VERSION__ >= 130
+ #define attribute in
+ #define varying out
+#endif
+
+#include es_precision.glsl
+
+#include mgl_const.glsl
+#include mgl_uniform.glsl
+#include mgl_attribute.glsl
+#include mgl_varying.glsl
+
+#include mgl_settexcoord.vp
+
+void main(void)
+{
+ if( mgl_ColorEnabled > 0 ) {
+ frontColor = mgl_Color;
+ } else {
+ frontColor = mgl_ColorStatic;
+ }
+
+ vec4 eyeCoord = mgl_PMVMatrix[1] * mgl_Vertex;
+ gl_Position = mgl_PMVMatrix[0] * eyeCoord;
+
+ float dist = distance(eyeCoord, vec4(0.0, 0.0, 0.0, 1.0));
+ float atten = sqrt( 1.0 / ( pointDistanceConstantAtten +
+ ( pointDistanceLinearAtten +
+ pointDistanceQuadraticAtten * dist
+ ) * dist
+ )
+ );
+ float size = clamp(pointSize * atten, pointSizeMin, pointSizeMax);
+ gl_PointSize = max(size, pointFadeThresholdSize);
+
+ float fade = min(size, pointFadeThresholdSize) / pointFadeThresholdSize;
+ frontColor.a *= fade * fade;
+}
+
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_alphatest.fp b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_alphatest.fp
new file mode 100644
index 000000000..2b64cdeb8
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_alphatest.fp
@@ -0,0 +1,33 @@
+
+void alphaTest(inout vec4 color) {
+ if( MGL_GREATER == mgl_AlphaTestFunc ) {
+ if ( color.a <= mgl_AlphaTestRef ) {
+ DISCARD(color);
+ }
+ } else if( MGL_LESS == mgl_AlphaTestFunc ) {
+ if ( color.a >= mgl_AlphaTestRef ) {
+ DISCARD(color);
+ }
+ } else if( MGL_LEQUAL == mgl_AlphaTestFunc ) {
+ if ( color.a > mgl_AlphaTestRef ) {
+ DISCARD(color);
+ }
+ } else if( MGL_GEQUAL == mgl_AlphaTestFunc ) {
+ if ( color.a < mgl_AlphaTestRef ) {
+ DISCARD(color);
+ }
+ } else if( MGL_EQUAL == mgl_AlphaTestFunc ) {
+ if ( abs( color.a - mgl_AlphaTestRef ) > EPSILON ) {
+ DISCARD(color);
+ }
+ } else if( MGL_NOTEQUAL == mgl_AlphaTestFunc ) {
+ if ( abs( color.a - mgl_AlphaTestRef ) <= EPSILON ) {
+ DISCARD(color);
+ }
+ } else if( MGL_NEVER == mgl_AlphaTestFunc ) {
+ DISCARD(color);
+ } /* else if( MGL_ALWAYS == mgl_AlphaTestFunc ) {
+ // NOP
+ } */
+}
+
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_attribute.glsl b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_attribute.glsl
index 09a11ec95..f670f7b77 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_attribute.glsl
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_attribute.glsl
@@ -4,16 +4,22 @@
#include es_precision.glsl
-attribute HIGHP vec4 mgl_Vertex;
-attribute HIGHP vec4 mgl_Normal;
-attribute HIGHP vec4 mgl_Color;
-attribute HIGHP vec4 mgl_MultiTexCoord0;
-attribute HIGHP vec4 mgl_MultiTexCoord1;
-attribute HIGHP vec4 mgl_MultiTexCoord2;
-attribute HIGHP vec4 mgl_MultiTexCoord3;
-attribute HIGHP vec4 mgl_MultiTexCoord4;
-attribute HIGHP vec4 mgl_MultiTexCoord5;
-attribute HIGHP vec4 mgl_MultiTexCoord6;
-attribute HIGHP vec4 mgl_MultiTexCoord7;
+attribute vec4 mgl_Vertex;
+attribute vec4 mgl_Normal;
+attribute vec4 mgl_Color;
+#if MAX_TEXTURE_UNITS >= 2
+attribute vec4 mgl_MultiTexCoord0;
+attribute vec4 mgl_MultiTexCoord1;
+#endif
+#if MAX_TEXTURE_UNITS >= 4
+attribute vec4 mgl_MultiTexCoord2;
+attribute vec4 mgl_MultiTexCoord3;
+#endif
+#if MAX_TEXTURE_UNITS >= 8
+attribute vec4 mgl_MultiTexCoord4;
+attribute vec4 mgl_MultiTexCoord5;
+attribute vec4 mgl_MultiTexCoord6;
+attribute vec4 mgl_MultiTexCoord7;
+#endif
#endif // mgl_attribute_glsl
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_const.glsl b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_const.glsl
index 1a464a1cb..4f97292e3 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_const.glsl
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_const.glsl
@@ -4,7 +4,36 @@
#include es_precision.glsl
-const LOWP int MAX_TEXTURE_UNITS = 8; // <=gl_MaxTextureImageUnits
+// will be defined at runtime: MAX_TEXTURE_UNITS [0|2|4|8]
const LOWP int MAX_LIGHTS = 8;
+const float EPSILON = 0.0000001; // FIXME: determine proper hw-precision
+
+// discard freezes NV tegra2 compiler (STILL TRUE?)
+// #define DISCARD(c) (c.a = 0.0)
+#define DISCARD(c) discard
+
+// Texture Environment / Multi Texturing
+#define MGL_ADD 1
+#define MGL_MODULATE 2
+#define MGL_DECAL 3
+#define MGL_BLEND 4
+#define MGL_REPLACE 5
+#define MGL_COMBINE 6
+
+// Alpha Test
+#define MGL_NEVER 1
+#define MGL_LESS 2
+#define MGL_EQUAL 3
+#define MGL_LEQUAL 4
+#define MGL_GREATER 5
+#define MGL_NOTEQUAL 6
+#define MGL_GEQUAL 7
+#define MGL_ALWAYS 8
+
+// Cull Face
+#define MGL_FRONT 1
+#define MGL_BACK 2
+#define MGL_FRONT_AND_BACK 3
+
#endif // mgl_const_glsl
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_lightdef.glsl b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_lightdef.glsl
index 98e214139..deaf95408 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_lightdef.glsl
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_lightdef.glsl
@@ -1,6 +1,9 @@
#ifndef mgl_lightdef_glsl
#define mgl_lightdef_glsl
+struct mgl_LightModelParameters {
+ vec4 ambient;
+};
struct mgl_LightSourceParameters {
vec4 ambient;
vec4 diffuse;
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_settexcoord.vp b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_settexcoord.vp
index 1efe328d0..cbf0db642 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_settexcoord.vp
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_settexcoord.vp
@@ -22,14 +22,20 @@ void setTexCoord(in vec4 defpos) {
mgl_TexCoords[7] = ( 0 != (mgl_TexCoordEnabled & 128) ) ? mgl_MultiTexCoord7 : defpos;
*/
+ #if MAX_TEXTURE_UNITS >= 2
mgl_TexCoords[0] = ( 0 != mgl_TexCoordEnabled[0] ) ? mgl_MultiTexCoord0 : defpos;
mgl_TexCoords[1] = ( 0 != mgl_TexCoordEnabled[1] ) ? mgl_MultiTexCoord1 : defpos;
+ #endif
+ #if MAX_TEXTURE_UNITS >= 4
mgl_TexCoords[2] = ( 0 != mgl_TexCoordEnabled[2] ) ? mgl_MultiTexCoord2 : defpos;
mgl_TexCoords[3] = ( 0 != mgl_TexCoordEnabled[3] ) ? mgl_MultiTexCoord3 : defpos;
+ #endif
+ #if MAX_TEXTURE_UNITS >= 8
mgl_TexCoords[4] = ( 0 != mgl_TexCoordEnabled[4] ) ? mgl_MultiTexCoord4 : defpos;
mgl_TexCoords[5] = ( 0 != mgl_TexCoordEnabled[5] ) ? mgl_MultiTexCoord5 : defpos;
mgl_TexCoords[6] = ( 0 != mgl_TexCoordEnabled[6] ) ? mgl_MultiTexCoord6 : defpos;
mgl_TexCoords[7] = ( 0 != mgl_TexCoordEnabled[7] ) ? mgl_MultiTexCoord7 : defpos;
+ #endif
}
#endif // mgl_settexcoord_vp
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_uniform.glsl b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_uniform.glsl
index 4c4000dfa..5029e4bd8 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_uniform.glsl
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_uniform.glsl
@@ -6,12 +6,45 @@
#include mgl_const.glsl
-uniform HIGHP mat4 mgl_PMVMatrix[4]; // P, Mv, Mvi and Mvit (transpose(inverse(ModelView)) == normalMatrix)
+uniform mat4 mgl_PMVMatrix[4]; // P, Mv, Mvi and Mvit (transpose(inverse(ModelView)) == normalMatrix)
uniform LOWP int mgl_ColorEnabled;
-uniform HIGHP vec4 mgl_ColorStatic;
+uniform vec4 mgl_ColorStatic;
+uniform LOWP int mgl_AlphaTestFunc;
+uniform float mgl_AlphaTestRef;
+
+// [0].rgba: size, smooth, attnMinSz, attnMaxSz
+// [1].rgba: attnCoeff(3), attnFadeTs
+uniform MEDIUMP vec4 mgl_PointParams[2];
+
+#define pointSize (mgl_PointParams[0].r)
+#define pointSmooth (mgl_PointParams[0].g)
+#define pointSizeMin (mgl_PointParams[0].b)
+#define pointSizeMax (mgl_PointParams[0].a)
+#define pointDistanceConstantAtten (mgl_PointParams[1].r)
+#define pointDistanceLinearAtten (mgl_PointParams[1].g)
+#define pointDistanceQuadraticAtten (mgl_PointParams[1].b)
+#define pointFadeThresholdSize (mgl_PointParams[1].a)
+
+// uniform LOWP int mgl_CullFace; // ES2 supports CullFace implicit ..
+#if MAX_TEXTURE_UNITS > 0
+uniform LOWP int mgl_TextureEnabled[MAX_TEXTURE_UNITS];
uniform LOWP int mgl_TexCoordEnabled[MAX_TEXTURE_UNITS];
-uniform sampler2D mgl_ActiveTexture;
-uniform LOWP int mgl_ActiveTextureIdx;
-uniform LOWP int mgl_CullFace;
+uniform LOWP int mgl_TexEnvMode[MAX_TEXTURE_UNITS];
+uniform LOWP int mgl_TexFormat[MAX_TEXTURE_UNITS];
+#if MAX_TEXTURE_UNITS >= 2
+uniform sampler2D mgl_Texture0;
+uniform sampler2D mgl_Texture1;
+#endif
+#if MAX_TEXTURE_UNITS >= 4
+uniform sampler2D mgl_Texture2;
+uniform sampler2D mgl_Texture3;
+#endif
+#if MAX_TEXTURE_UNITS >= 8
+uniform sampler2D mgl_Texture4;
+uniform sampler2D mgl_Texture5;
+uniform sampler2D mgl_Texture6;
+uniform sampler2D mgl_Texture7;
+#endif
+#endif
#endif // mgl_uniform_glsl
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_uniform_light.glsl b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_uniform_light.glsl
index 0dedb5d5d..5b34fd9cf 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_uniform_light.glsl
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_uniform_light.glsl
@@ -9,6 +9,7 @@
uniform LOWP int mgl_LightsEnabled[MAX_LIGHTS];
+uniform mgl_LightModelParameters mgl_LightModel;
uniform mgl_LightSourceParameters mgl_LightSource[MAX_LIGHTS];
uniform mgl_MaterialParameters mgl_FrontMaterial;
diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_varying.glsl b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_varying.glsl
index fc9f735d1..599ac4a53 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_varying.glsl
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/mgl_varying.glsl
@@ -7,6 +7,8 @@
#include mgl_const.glsl
varying vec4 frontColor;
+#if MAX_TEXTURE_UNITS > 0
varying vec4 mgl_TexCoords[MAX_TEXTURE_UNITS];
+#endif
#endif // mgl_varying_glsl
diff --git a/src/jogl/classes/jogamp/opengl/util/jpeg/JPEGDecoder.java b/src/jogl/classes/jogamp/opengl/util/jpeg/JPEGDecoder.java
new file mode 100644
index 000000000..e3e43b30c
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/jpeg/JPEGDecoder.java
@@ -0,0 +1,1520 @@
+/**
+ * Original JavaScript code from <https://github.com/notmasteryet/jpgjs/blob/master/jpg.js>,
+ * ported to Java for JogAmp Community.
+ *
+ * Enhancements:
+ * * InputStream instead of memory buffer
+ * * User provided memory handler
+ * * Fixed JPEG Component ID/Index mapping
+ * * Color space conversion (YCCK, CMYK -> RGB)
+ * * More error tolerant
+ *
+ * *****************
+ *
+ * Copyright 2011 notmasteryet
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * *****************
+ *
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+
+package jogamp.opengl.util.jpeg;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import jogamp.opengl.Debug;
+
+import com.jogamp.common.util.ArrayHashSet;
+import com.jogamp.common.util.Bitstream;
+import com.jogamp.common.util.VersionNumber;
+import com.jogamp.opengl.util.texture.TextureData;
+import com.jogamp.opengl.util.texture.TextureData.ColorSpace;
+
+/**
+ *
+ * <ul>
+ * <li> The JPEG specification can be found in the ITU CCITT Recommendation T.81
+ * (www.w3.org/Graphics/JPEG/itu-t81.pdf) </li>
+ * <li> The JFIF specification can be found in the JPEG File Interchange Format
+ * (www.w3.org/Graphics/JPEG/jfif3.pdf)</li>
+ * <li> The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters
+ * in PostScript Level 2, Technical Note #5116
+ * (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)</li>
+ * <li> http://halicery.com/jpeg/huffman.html </li>
+ * <li> https://en.wikipedia.org/wiki/Jpg#Syntax_and_structure </li>
+ * <li> http://www.cs.sfu.ca/CourseCentral/365/mark/material/notes/Chap4/Chap4.2/Chap4.2.html </li>
+ * <li> https://github.com/notmasteryet/jpgjs/blob/master/jpg.js </li>
+ * </ul>
+ */
+public class JPEGDecoder {
+ private static final boolean DEBUG = Debug.debug("JPEGImage");
+ private static final boolean DEBUG_IN = false;
+
+ /** Allows user to hook a {@link ColorSink} to another toolkit to produce {@link TextureData}. */
+ public static interface ColorSink {
+ /**
+ * @param width
+ * @param height
+ * @param sourceCS the color-space of the decoded JPEG
+ * @param sourceComponents number of components used for the given source color-space
+ * @return Either {@link TextureData.ColorSpace#RGB} or {@link TextureData.ColorSpace#YCbCr}. {@link TextureData.ColorSpace#YCCK} and {@link TextureData.ColorSpace#CMYK} will throw an exception!
+ * @throws RuntimeException
+ */
+ public TextureData.ColorSpace allocate(int width, int height, TextureData.ColorSpace sourceCS, int sourceComponents) throws RuntimeException;
+ public void store2(int x, int y, byte c1, byte c2);
+ public void storeRGB(int x, int y, byte r, byte g, byte b);
+ public void storeYCbCr(int x, int y, byte Y, byte Cb, byte Cr);
+ }
+
+ public static class JFIF {
+ final VersionNumber version;
+ final int densityUnits;
+ final int xDensity;
+ final int yDensity;
+ final int thumbWidth;
+ final int thumbHeight;
+ final byte[] thumbData;
+
+ private JFIF(final byte data[]) {
+ version = new VersionNumber(data[5], data[6], 0);
+ densityUnits = data[7];
+ xDensity = ((data[ 8] << 8) & 0xff00) | (data[ 9] & 0xff);
+ yDensity = ((data[10] << 8) & 0xff00) | (data[11] & 0xff);
+ thumbWidth = data[12];
+ thumbHeight = data[13];
+ if( 0 < thumbWidth && 0 < thumbHeight ) {
+ final int len = 14 + 3 * thumbWidth * thumbHeight;
+ thumbData = new byte[len];
+ System.arraycopy(data, 14, thumbData, 0, len);
+ } else {
+ thumbData = null;
+ }
+ }
+
+ public static final JFIF get(final byte[] data) throws RuntimeException {
+ if ( data[0] == (byte)0x4A && data[1] == (byte)0x46 && data[2] == (byte)0x49 &&
+ data[3] == (byte)0x46 && data[4] == (byte)0x0) { // 'JFIF\x00'
+ final JFIF r = new JFIF(data);
+ return r;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public final String toString() {
+ return "JFIF[ver "+version+", density[units "+densityUnits+", "+xDensity+"x"+yDensity+"], thumb "+thumbWidth+"x"+thumbHeight+"]";
+ }
+ }
+
+ public static class Adobe {
+ final short version;
+ final short flags0;
+ final short flags1;
+ final short colorCode;
+ final ColorSpace colorSpace;
+
+ private Adobe(final byte[] data) {
+ version = data[6];
+ flags0 = (short)(((data[7] << 8) & 0xff00) | (data[ 8] & 0xff));
+ flags1 = (short)(((data[9] << 8) & 0xff00) | (data[10] & 0xff));
+ colorCode = data[11];
+ switch( colorCode ) {
+ case 2: colorSpace = ColorSpace.YCCK; break;
+ case 1: colorSpace = ColorSpace.YCbCr; break;
+ default: colorSpace = ColorSpace.CMYK; break;
+ }
+ }
+ public static final Adobe get(final byte[] data) throws RuntimeException {
+ if (data[0] == (byte)0x41 && data[1] == (byte)0x64 && data[2] == (byte)0x6F &&
+ data[3] == (byte)0x62 && data[4] == (byte)0x65 && data[5] == (byte)0) { // 'Adobe\x00'
+ final Adobe r = new Adobe(data);
+ return r;
+ } else {
+ return null;
+ }
+ }
+ @Override
+ public final String toString() {
+ return "Adobe[ver "+version+", flags["+toHexString(flags0)+", "+toHexString(flags1)+"], colorSpace/Code "+colorSpace+"/"+toHexString(colorCode)+"]";
+ }
+ }
+ /** TODO */
+ public static class EXIF {
+ private EXIF(final byte data[]) {
+ }
+
+ public static final EXIF get(final byte[] data) throws RuntimeException {
+ if ( data[0] == (byte)0x45 && data[1] == (byte)0x78 && data[2] == (byte)0x69 &&
+ data[3] == (byte)0x66 && data[4] == (byte)0x0) { // 'Exif\x00'
+ final EXIF r = new EXIF(data);
+ return r;
+ } else {
+ return null;
+ }
+ }
+ @Override
+ public final String toString() {
+ return "EXIF[]";
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static class CodecException extends RuntimeException {
+ CodecException(final String message) {
+ super(message);
+ }
+ }
+ @SuppressWarnings("serial")
+ public static class MarkerException extends CodecException {
+ final int marker;
+ MarkerException(final int marker, final String message) {
+ super(message+" - Marker "+toHexString(marker));
+ this.marker = marker;
+ }
+ public int getMarker() { return marker; }
+ }
+
+ /** Start of Image */
+ private static final int M_SOI = 0xFFD8;
+ /** End of Image */
+ private static final int M_EOI = 0xFFD9;
+ /** Start of Frame - Baseline DCT */
+ private static final int M_SOF0 = 0xFFC0;
+ /** Start of Frame - Extended sequential DCT */
+ // private static final int M_SOF1 = 0xFFC1;
+ /** Start of Frame - Progressive DCT */
+ private static final int M_SOF2 = 0xFFC2;
+ /** DHT (Define Huffman Tables) */
+ private static final int M_DHT = 0xFFC4;
+ // private static final int M_DAC = 0xFFCC;
+ /** SOS (Start of Scan) */
+ private static final int M_SOS = 0xFFDA;
+ /** DQT (Define Quantization Tables) */
+ private static final int M_QTT = 0xFFDB;
+ /** DRI (Define Restart Interval) */
+ private static final int M_DRI = 0xFFDD;
+ /** APP0 (Application Specific) - JFIF Header */
+ private static final int M_APP00 = 0xFFE0;
+ /** APP1 (Application Specific) - Exif Header */
+ private static final int M_APP01 = 0xFFE1;
+ /** APP2 (Application Specific) */
+ private static final int M_APP02 = 0xFFE2;
+ /** APP3 (Application Specific) */
+ private static final int M_APP03 = 0xFFE3;
+ /** APP4 (Application Specific) */
+ private static final int M_APP04 = 0xFFE4;
+ /** APP5 (Application Specific) */
+ private static final int M_APP05 = 0xFFE5;
+ /** APP6 (Application Specific) */
+ private static final int M_APP06 = 0xFFE6;
+ /** APP7 (Application Specific) */
+ private static final int M_APP07 = 0xFFE7;
+ /** APP8 (Application Specific) */
+ private static final int M_APP08 = 0xFFE8;
+ /** APP9 (Application Specific) */
+ private static final int M_APP09 = 0xFFE9;
+ /** APP10 (Application Specific) */
+ private static final int M_APP10 = 0xFFEA;
+ /** APP11 (Application Specific) */
+ private static final int M_APP11 = 0xFFEB;
+ /** APP12 (Application Specific) */
+ private static final int M_APP12 = 0xFFEC;
+ /** APP13 (Application Specific) */
+ private static final int M_APP13 = 0xFFED;
+ /** APP14 (Application Specific) - ADOBE Header */
+ private static final int M_APP14 = 0xFFEE;
+ /** APP15 (Application Specific) */
+ private static final int M_APP15 = 0xFFEF;
+
+ /** Annotation / Comment */
+ private static final int M_ANO = 0xFFFE;
+
+ static final int[] dctZigZag = new int[] {
+ 0,
+ 1, 8,
+ 16, 9, 2,
+ 3, 10, 17, 24,
+ 32, 25, 18, 11, 4,
+ 5, 12, 19, 26, 33, 40,
+ 48, 41, 34, 27, 20, 13, 6,
+ 7, 14, 21, 28, 35, 42, 49, 56,
+ 57, 50, 43, 36, 29, 22, 15,
+ 23, 30, 37, 44, 51, 58,
+ 59, 52, 45, 38, 31,
+ 39, 46, 53, 60,
+ 61, 54, 47,
+ 55, 62,
+ 63
+ };
+
+ static final int dctCos1 = 4017; // cos(pi/16)
+ static final int dctSin1 = 799; // sin(pi/16)
+ static final int dctCos3 = 3406; // cos(3*pi/16)
+ static final int dctSin3 = 2276; // sin(3*pi/16)
+ static final int dctCos6 = 1567; // cos(6*pi/16)
+ static final int dctSin6 = 3784; // sin(6*pi/16)
+ static final int dctSqrt2 = 5793; // sqrt(2)
+ static final int dctSqrt1d2 = 2896; // sqrt(2) / 2
+
+ static class Frame {
+ final boolean progressive;
+ final int precision;
+ final int scanLines;
+ final int samplesPerLine;
+ private final ArrayHashSet<Integer> compIDs;
+ private final ComponentIn[] comps;
+ private final int compCount;
+ /** quantization tables */
+ final int[][] qtt;
+ int maxCompID;
+ int maxH;
+ int maxV;
+ int mcusPerLine;
+ int mcusPerColumn;
+
+ Frame(final boolean progressive, final int precision, final int scanLines, final int samplesPerLine, final int componentsCount, final int[][] qtt) {
+ this.progressive = progressive;
+ this.precision = precision;
+ this.scanLines = scanLines;
+ this.samplesPerLine = samplesPerLine;
+ compIDs = new ArrayHashSet<Integer>(componentsCount);
+ comps = new ComponentIn[componentsCount];
+ this.compCount = componentsCount;
+ this.qtt = qtt;
+ }
+
+ private final void checkBounds(final int idx) {
+ if( 0 > idx || idx >= compCount ) {
+ throw new CodecException("Idx out of bounds "+idx+", "+this);
+ }
+ }
+ public final void validateComponents() {
+ for(int i=0; i<compCount; i++) {
+ final ComponentIn c = comps[i];
+ if( null == c ) {
+ throw new CodecException("Component["+i+"] null");
+ }
+ if( null == this.qtt[c.qttIdx] ) {
+ throw new CodecException("Component["+i+"].qttIdx -> null QTT");
+ }
+ }
+ }
+
+ public final int getCompCount() { return compCount; }
+ public final int getMaxCompID() { return maxCompID; }
+
+ public final void putOrdered(final int compID, final ComponentIn component) {
+ if( maxCompID < compID ) {
+ maxCompID = compID;
+ }
+ final int idx = compIDs.size();
+ checkBounds(idx);
+ compIDs.add(compID);
+ comps[idx] = component;
+ }
+ public final ComponentIn getCompByIndex(final int i) {
+ checkBounds(i);
+ return comps[i];
+ }
+ public final ComponentIn getCompByID(final int componentID) {
+ return getCompByIndex( compIDs.indexOf(componentID) );
+ }
+ public final int getCompID(final int idx) {
+ return compIDs.get(idx);
+ }
+ public final boolean hasCompID(final int componentID) {
+ return compIDs.contains(componentID);
+ }
+ @Override
+ public final String toString() {
+ return "Frame[progressive "+progressive+", precision "+precision+", scanLines "+scanLines+", samplesPerLine "+samplesPerLine+
+ ", components[count "+compCount+", maxID "+maxCompID+", componentIDs "+compIDs+", comps "+Arrays.asList(comps)+"]]";
+ }
+ }
+
+ /** The JPEG encoded components */
+ static class ComponentIn {
+ final int h, v;
+ /** index to frame.qtt[] */
+ final int qttIdx;
+ int blocksPerColumn;
+ int blocksPerColumnForMcu;
+ int blocksPerLine;
+ int blocksPerLineForMcu;
+ /** [blocksPerColumnForMcu][blocksPerLineForMcu][64]; */
+ int[][][] blocks;
+ int pred;
+ BinObj huffmanTableAC;
+ BinObj huffmanTableDC;
+
+ ComponentIn(final int h, final int v, final int qttIdx) {
+ this.h = h;
+ this.v = v;
+ this.qttIdx = qttIdx;
+ }
+
+ public final void allocateBlocks(final int blocksPerColumn, final int blocksPerColumnForMcu, final int blocksPerLine, final int blocksPerLineForMcu) {
+ this.blocksPerColumn = blocksPerColumn;
+ this.blocksPerColumnForMcu = blocksPerColumnForMcu;
+ this.blocksPerLine = blocksPerLine;
+ this.blocksPerLineForMcu = blocksPerLineForMcu;
+ this.blocks = new int[blocksPerColumnForMcu][blocksPerLineForMcu][64];
+ }
+ public final int[] getBlock(final int row, final int col) {
+ if( row >= blocksPerColumnForMcu || col >= blocksPerLineForMcu ) {
+ throw new CodecException("Out of bounds given ["+row+"]["+col+"] - "+this);
+ }
+ return blocks[row][col];
+ }
+
+ @Override
+ public final String toString() {
+ return "CompIn[h "+h+", v "+v+", qttIdx "+qttIdx+", blocks["+blocksPerColumn+", mcu "+blocksPerColumnForMcu+"]["+blocksPerLine+", mcu "+blocksPerLineForMcu+"][64]]";
+ }
+ }
+
+ /** The decoded components */
+ static class ComponentOut {
+ private final ArrayList<byte[]> lines;
+ final float scaleX;
+ final float scaleY;
+
+ ComponentOut(final ArrayList<byte[]> lines, final float scaleX, final float scaleY) {
+ this.lines = lines;
+ this.scaleX = scaleX;
+ this.scaleY = scaleY;
+ }
+
+ /** Safely returning a line, if index exceeds number of lines, last line is returned. */
+ public final byte[] getLine(final int i) {
+ final int sz = lines.size();
+ return lines.get( i < sz ? i : sz - 1);
+ }
+
+ @Override
+ public final String toString() {
+ return "CompOut[lines "+lines.size()+", scale "+scaleX+"x"+scaleY+"]";
+ }
+ }
+
+ @Override
+ public String toString() {
+ final String jfifS = null != jfif ? jfif.toString() : "JFIF nil";
+ final String exifS = null != exif ? exif.toString() : "Exif nil";
+ final String adobeS = null != adobe ? adobe.toString() : "Adobe nil";
+ final String compOuts = null != components ? Arrays.asList(components).toString() : "nil";
+ return "JPEG[size "+width+"x"+height+", compOut "+compOuts+", "+jfifS+", "+exifS+", "+adobeS+"]";
+ }
+
+ private final Bitstream<InputStream> bstream = new Bitstream<InputStream>(new Bitstream.ByteInputStream(null), false /* outputMode */);
+
+ private int width = 0;
+ private int height = 0;
+ private JFIF jfif = null;
+ private EXIF exif = null;
+ private Adobe adobe = null;
+ private ComponentOut[] components = null;
+
+ public final JFIF getJFIFHeader() { return jfif; }
+ public final EXIF getEXIFHeader() { return exif; }
+ public final Adobe getAdobeHeader() { return adobe; }
+ public final int getWidth() { return width; }
+ public final int getHeight() { return height; }
+
+ private final void setStream(final InputStream is) {
+ try {
+ bstream.setStream(is, false /* outputMode */);
+ } catch (final Exception e) {
+ throw new RuntimeException(e); // should not happen, no flush()
+ }
+ }
+
+ private final int readUInt8() throws IOException {
+ return bstream.readUInt8(true /* msbFirst */);
+ }
+
+ private final int readUInt16() throws IOException {
+ return bstream.readUInt16(true /* msbFirst */, true /* bigEndian */);
+ }
+
+ private final int readNumber() throws IOException {
+ final int len=readUInt16();
+ if(len!=4){
+ throw new CodecException("ERROR: Define number format error [Len!=4, but "+len+"]");
+ }
+ return readUInt16();
+ }
+
+ private final byte[] readDataBlock() throws IOException {
+ int count=0, i=0;
+ final int len=readUInt16(); count+=2;
+ final byte[] data = new byte[len-2];
+ while(count<len){
+ data[i++] = (byte)readUInt8(); count++;
+ }
+ if(DEBUG_IN) { System.err.println("JPEG.readDataBlock: net-len "+(len-2)+", "+this); dumpData(data, 0, len-2); }
+ return data;
+ }
+ static final void dumpData(final byte[] data, final int offset, final int len) {
+ for(int i=0; i<len; ) {
+ System.err.print(i%8+": ");
+ for(int j=0; j<8 && i<len; j++, i++) {
+ System.err.print(toHexString(0x000000FF & data[offset+i])+", ");
+ }
+ System.err.println("");
+ }
+ }
+
+ public synchronized void clear(final InputStream inputStream) {
+ setStream(inputStream);
+ width = 0;
+ height = 0;
+ jfif = null;
+ exif = null;
+ adobe = null;
+ components = null;
+ }
+ public synchronized JPEGDecoder parse(final InputStream inputStream) throws IOException {
+ clear(inputStream);
+
+ final int[][] quantizationTables = new int[0x0F][]; // 4 bits
+ final BinObj[] huffmanTablesAC = new BinObj[0x0F]; // Huffman table spec - 4 bits
+ final BinObj[] huffmanTablesDC = new BinObj[0x0F]; // Huffman table spec - 4 bits
+ // final ArrayList<Frame> frames = new ArrayList<Frame>(); // JAU: max 1-frame
+
+ Frame frame = null;
+ int resetInterval = 0;
+ int fileMarker = readUInt16();
+ if ( fileMarker != M_SOI ) {
+ throw new CodecException("SOI not found, but has marker "+toHexString(fileMarker));
+ }
+
+ fileMarker = readUInt16();
+ while (fileMarker != M_EOI) {
+ if(DEBUG) { System.err.println("JPG.parse got marker "+toHexString(fileMarker)); }
+ switch(fileMarker) {
+ case M_APP00:
+ case M_APP01:
+ case M_APP02:
+ case M_APP03:
+ case M_APP04:
+ case M_APP05:
+ case M_APP06:
+ case M_APP07:
+ case M_APP08:
+ case M_APP09:
+ case M_APP10:
+ case M_APP11:
+ case M_APP12:
+ case M_APP13:
+ case M_APP14:
+ case M_APP15:
+ case M_ANO: {
+ final byte[] appData = readDataBlock();
+
+ if ( fileMarker == M_APP00 ) {
+ jfif = JFIF.get( appData );
+ }
+ if ( fileMarker == M_APP01 ) {
+ exif = EXIF.get(appData);
+ }
+ if (fileMarker == M_APP14) {
+ adobe = Adobe.get(appData);
+ }
+ fileMarker = 0; // consumed and get-next
+ }
+ break;
+
+ case M_QTT: {
+ int count = 0;
+ final int quantizationTablesLength = readUInt16(); count+=2;
+ while( count < quantizationTablesLength ) {
+ final int quantizationTableSpec = readUInt8(); count++;
+ final int precisionID = quantizationTableSpec >> 4;
+ final int tableIdx = quantizationTableSpec & 0x0F;
+ final int[] tableData = new int[64];
+ if ( precisionID == 0 ) { // 8 bit values
+ for (int j = 0; j < 64; j++) {
+ final int z = dctZigZag[j];
+ tableData[z] = readUInt8(); count++;
+ }
+ } else if ( precisionID == 1) { //16 bit
+ for (int j = 0; j < 64; j++) {
+ final int z = dctZigZag[j];
+ tableData[z] = readUInt16(); count+=2;
+ }
+ } else {
+ throw new CodecException("DQT: invalid table precision "+precisionID+", quantizationTableSpec "+quantizationTableSpec+", idx "+tableIdx);
+ }
+ quantizationTables[tableIdx] = tableData;
+ if( DEBUG ) {
+ System.err.println("JPEG.parse.QTT["+tableIdx+"]: spec "+quantizationTableSpec+", precision "+precisionID+", data "+count+"/"+quantizationTablesLength);
+ }
+ }
+ if(count!=quantizationTablesLength){
+ throw new CodecException("ERROR: QTT format error [count!=Length]: "+count+"/"+quantizationTablesLength);
+ }
+ fileMarker = 0; // consumed and get-next
+ }
+ break;
+
+ case M_SOF0:
+ case M_SOF2: {
+ if( null != frame ) { // JAU: max 1-frame
+ throw new CodecException("only single frame JPEGs supported");
+ }
+ int count = 0;
+ final int sofLen = readUInt16(); count+=2; // header length;
+ final int componentsCount;
+ {
+ final boolean progressive = (fileMarker == M_SOF2);
+ final int precision = readUInt8(); count++;
+ final int scanLines = readUInt16(); count+=2;
+ final int samplesPerLine = readUInt16(); count+=2;
+ componentsCount = readUInt8(); count++;
+ frame = new Frame(progressive, precision, scanLines, samplesPerLine, componentsCount, quantizationTables);
+ width = frame.samplesPerLine;
+ height = frame.scanLines;
+ }
+ for (int i = 0; i < componentsCount; i++) {
+ final int componentId = readUInt8(); count++;
+ final int temp = readUInt8(); count++;
+ final int h = temp >> 4;
+ final int v = temp & 0x0F;
+ final int qttIdx = readUInt8(); count++;
+ final ComponentIn compIn = new ComponentIn(h, v, qttIdx);
+ frame.putOrdered(componentId, compIn);
+ }
+ if(count!=sofLen){
+ throw new CodecException("ERROR: SOF format error [count!=Length]");
+ }
+ prepareComponents(frame);
+ // frames.add(frame); // JAU: max 1-frame
+ if(DEBUG) { System.err.println("JPG.parse.SOF[02]: Got frame "+frame); }
+ fileMarker = 0; // consumed and get-next
+ }
+ break;
+
+ case M_DHT: {
+ int count = 0;
+ final int huffmanLength = readUInt16(); count+=2;
+ int i=count, codeLengthTotal = 0;
+ while( i < huffmanLength ) {
+ final int huffmanTableSpec = readUInt8(); count++;
+ final int[] codeLengths = new int[16];
+ int codeLengthSum = 0;
+ for (int j = 0; j < 16; j++) {
+ codeLengthSum += (codeLengths[j] = readUInt8()); count++;
+ }
+ final byte[] huffmanValues = new byte[codeLengthSum];
+ for (int j = 0; j < codeLengthSum; j++) {
+ huffmanValues[j] = (byte)readUInt8(); count++;
+ }
+ codeLengthTotal += codeLengthSum;
+ i += 17 + codeLengthSum;
+ final BinObj[] table = ( huffmanTableSpec >> 4 ) == 0 ? huffmanTablesDC : huffmanTablesAC;
+ table[huffmanTableSpec & 0x0F] = buildHuffmanTable(codeLengths, huffmanValues);
+ }
+ if(count!=huffmanLength || i!=count){
+ throw new CodecException("ERROR: Huffman table format error [count!=Length]");
+ }
+ if(DEBUG) { System.err.println("JPG.parse.DHT: Got Huffman CodeLengthTotal "+codeLengthTotal); }
+ fileMarker = 0; // consumed and get-next
+ }
+ break;
+
+ case M_DRI:
+ resetInterval = readNumber();
+ if(DEBUG) { System.err.println("JPG.parse.DRI: Got Reset Interval "+resetInterval); }
+ fileMarker = 0; // consumed and get-next
+ break;
+
+ case M_SOS: {
+ int count = 0;
+ final int sosLen = readUInt16(); count+=2;
+ final int selectorsCount = readUInt8(); count++;
+ final ArrayList<ComponentIn> components = new ArrayList<ComponentIn>();
+ if(DEBUG) { System.err.println("JPG.parse.SOS: selectorCount [0.."+(selectorsCount-1)+"]: "+frame); }
+ for (int i = 0; i < selectorsCount; i++) {
+ final int compID = readUInt8(); count++;
+ final ComponentIn component = frame.getCompByID(compID);
+ final int tableSpec = readUInt8(); count++;
+ component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
+ component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
+ components.add(component);
+ }
+ final int spectralStart = readUInt8(); count++;
+ final int spectralEnd = readUInt8(); count++;
+ final int successiveApproximation = readUInt8(); count++;
+ if(count!=sosLen){
+ throw new CodecException("ERROR: scan header format error [count!=Length]");
+ }
+ fileMarker = decoder.decodeScan(frame, components, resetInterval,
+ spectralStart, spectralEnd,
+ successiveApproximation >> 4, successiveApproximation & 15);
+ if(DEBUG) { System.err.println("JPG.parse.SOS.decode result "+toHexString(fileMarker)); }
+ }
+ break;
+ default:
+ /**
+ if (data[offset - 3] == 0xFF &&
+ data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
+ // could be incorrect encoding -- last 0xFF byte of the previous
+ // block was eaten by the encoder
+ offset -= 3;
+ break;
+ } */
+ throw new CodecException("unknown JPEG marker " + toHexString(fileMarker) + ", " + bstream);
+ }
+ if( 0 == fileMarker ) {
+ fileMarker = readUInt16();
+ }
+ }
+ if(DEBUG) { System.err.println("JPG.parse.2: End of parsing input "+this); }
+ /** // JAU: max 1-frame
+ if ( frames.size() != 1 ) {
+ throw new CodecException("only single frame JPEGs supported "+this);
+ } */
+ if( null == frame ) {
+ throw new CodecException("no single frame found in stream "+this);
+ }
+ frame.validateComponents();
+
+ final int compCount = frame.getCompCount();
+ this.components = new ComponentOut[compCount];
+ for (int i = 0; i < compCount; i++) {
+ final ComponentIn component = frame.getCompByIndex(i);
+ // System.err.println("JPG.parse.buildComponentData["+i+"]: "+component); // JAU
+ // System.err.println("JPG.parse.buildComponentData["+i+"]: "+frame); // JAU
+ this.components[i] = new ComponentOut( output.buildComponentData(frame, component),
+ (float)component.h / (float)frame.maxH,
+ (float)component.v / (float)frame.maxV );
+ }
+ if(DEBUG) { System.err.println("JPG.parse.X: End of processing input "+this); }
+ return this;
+ }
+
+ private void prepareComponents(final Frame frame) {
+ int maxH = 0, maxV = 0;
+ // for (componentId in frame.components) {
+ final int compCount = frame.getCompCount();
+ for (int i=0; i<compCount; i++) {
+ final ComponentIn component = frame.getCompByIndex(i);
+ if (maxH < component.h) maxH = component.h;
+ if (maxV < component.v) maxV = component.v;
+ }
+ final int mcusPerLine = (int) Math.ceil(frame.samplesPerLine / 8f / maxH);
+ final int mcusPerColumn = (int) Math.ceil(frame.scanLines / 8f / maxV);
+ // for (componentId in frame.components) {
+ for (int i=0; i<compCount; i++) {
+ final ComponentIn component = frame.getCompByIndex(i);
+ final int blocksPerLine = (int) Math.ceil(Math.ceil(frame.samplesPerLine / 8f) * component.h / maxH);
+ final int blocksPerColumn = (int) Math.ceil(Math.ceil(frame.scanLines / 8f) * component.v / maxV);
+ final int blocksPerLineForMcu = mcusPerLine * component.h;
+ final int blocksPerColumnForMcu = mcusPerColumn * component.v;
+ component.allocateBlocks(blocksPerColumn, blocksPerColumnForMcu, blocksPerLine, blocksPerLineForMcu);
+ }
+ frame.maxH = maxH;
+ frame.maxV = maxV;
+ frame.mcusPerLine = mcusPerLine;
+ frame.mcusPerColumn = mcusPerColumn;
+ }
+
+ static class BinObjIdxed {
+ final BinObj children;
+ byte index;
+ BinObjIdxed() {
+ this.children = new BinObj();
+ this.index = 0;
+ }
+ }
+ static class BinObj {
+ final boolean isValue;
+ final BinObj[] tree;
+ final byte b;
+
+ BinObj(final byte b) {
+ this.isValue= true;
+ this.b = b;
+ this.tree = null;
+ }
+ BinObj() {
+ this.isValue= false;
+ this.b = (byte)0;
+ this.tree = new BinObj[2];
+ }
+ final byte getValue() { return b; }
+ final BinObj get(final int i) { return tree[i]; }
+ final void set(final byte i, final byte v) { tree[i] = new BinObj(v); }
+ final void set(final byte i, final BinObj v) { tree[i] = v; }
+ }
+
+ private BinObj buildHuffmanTable(final int[] codeLengths, final byte[] values) {
+ int k = 0;
+ int length = 16;
+ final ArrayList<BinObjIdxed> code = new ArrayList<BinObjIdxed>();
+ while (length > 0 && 0==codeLengths[length - 1]) {
+ length--;
+ }
+ code.add(new BinObjIdxed());
+ BinObjIdxed p = code.get(0), q;
+ for (int i = 0; i < length; i++) {
+ for (int j = 0; j < codeLengths[i]; j++) {
+ p = code.remove(code.size()-1);
+ p.children.set(p.index, values[k]);
+ while (p.index > 0) {
+ p = code.remove(code.size()-1);
+ }
+ p.index++;
+ code.add(p);
+ while (code.size() <= i) {
+ q = new BinObjIdxed();
+ code.add(q);
+ p.children.set(p.index, q.children);
+ p = q;
+ }
+ k++;
+ }
+ if (i + 1 < length) {
+ // p here points to last code
+ q = new BinObjIdxed();
+ code.add(q);
+ p.children.set(p.index, q.children);
+ p = q;
+ }
+ }
+ return code.get(0).children;
+ }
+
+ private final Output output = new Output();
+ static class Output {
+ private int blocksPerLine;
+ private int blocksPerColumn;
+ private int samplesPerLine;
+
+ private ArrayList<byte[]> buildComponentData(final Frame frame, final ComponentIn component) {
+ final ArrayList<byte[]> lines = new ArrayList<byte[]>();
+ blocksPerLine = component.blocksPerLine;
+ blocksPerColumn = component.blocksPerColumn;
+ samplesPerLine = blocksPerLine << 3;
+ final int[] R = new int[64];
+ final byte[] r = new byte[64];
+
+ for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
+ final int scanLine = blockRow << 3;
+ // System.err.println("JPG.buildComponentData: row "+blockRow+"/"+blocksPerColumn+" -> scanLine "+scanLine); // JAU
+ for (int i = 0; i < 8; i++) {
+ lines.add(new byte[samplesPerLine]);
+ }
+ for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) {
+ // System.err.println("JPG.buildComponentData: col "+blockCol+"/"+blocksPerLine+", comp.qttIdx "+component.qttIdx+", qtt "+frame.qtt[component.qttIdx]); // JAU
+ quantizeAndInverse(component.getBlock(blockRow, blockCol), r, R, frame.qtt[component.qttIdx]);
+
+ final int sample = blockCol << 3;
+ int offset = 0;
+ for (int j = 0; j < 8; j++) {
+ final byte[] line = lines.get(scanLine + j);
+ for (int i = 0; i < 8; i++)
+ line[sample + i] = r[offset++];
+ }
+ }
+ }
+ return lines;
+ }
+
+ // A port of poppler's IDCT method which in turn is taken from:
+ // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
+ // "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
+ // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
+ // 988-991.
+ private void quantizeAndInverse(final int[] zz, final byte[] dataOut, final int[] dataIn, final int[] qt) {
+ int v0, v1, v2, v3, v4, v5, v6, v7, t;
+ final int[] p = dataIn;
+ int i;
+
+ // dequant
+ for (i = 0; i < 64; i++) {
+ p[i] = zz[i] * qt[i];
+ }
+
+ // inverse DCT on rows
+ for (i = 0; i < 8; ++i) {
+ final int row = 8 * i;
+
+ // check for all-zero AC coefficients
+ if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 &&
+ p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 &&
+ p[7 + row] == 0) {
+ t = (dctSqrt2 * p[0 + row] + 512) >> 10;
+ p[0 + row] = t;
+ p[1 + row] = t;
+ p[2 + row] = t;
+ p[3 + row] = t;
+ p[4 + row] = t;
+ p[5 + row] = t;
+ p[6 + row] = t;
+ p[7 + row] = t;
+ continue;
+ }
+
+ // stage 4
+ v0 = (dctSqrt2 * p[0 + row] + 128) >> 8;
+ v1 = (dctSqrt2 * p[4 + row] + 128) >> 8;
+ v2 = p[2 + row];
+ v3 = p[6 + row];
+ v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8;
+ v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8;
+ v5 = p[3 + row] << 4;
+ v6 = p[5 + row] << 4;
+
+ // stage 3
+ t = (v0 - v1+ 1) >> 1;
+ v0 = (v0 + v1 + 1) >> 1;
+ v1 = t;
+ t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8;
+ v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8;
+ v3 = t;
+ t = (v4 - v6 + 1) >> 1;
+ v4 = (v4 + v6 + 1) >> 1;
+ v6 = t;
+ t = (v7 + v5 + 1) >> 1;
+ v5 = (v7 - v5 + 1) >> 1;
+ v7 = t;
+
+ // stage 2
+ t = (v0 - v3 + 1) >> 1;
+ v0 = (v0 + v3 + 1) >> 1;
+ v3 = t;
+ t = (v1 - v2 + 1) >> 1;
+ v1 = (v1 + v2 + 1) >> 1;
+ v2 = t;
+ t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+ v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+ v7 = t;
+ t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+ v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+ v6 = t;
+
+ // stage 1
+ p[0 + row] = v0 + v7;
+ p[7 + row] = v0 - v7;
+ p[1 + row] = v1 + v6;
+ p[6 + row] = v1 - v6;
+ p[2 + row] = v2 + v5;
+ p[5 + row] = v2 - v5;
+ p[3 + row] = v3 + v4;
+ p[4 + row] = v3 - v4;
+ }
+
+ // inverse DCT on columns
+ for (i = 0; i < 8; ++i) {
+ final int col = i;
+
+ // check for all-zero AC coefficients
+ if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 &&
+ p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 &&
+ p[7*8 + col] == 0) {
+ t = (dctSqrt2 * dataIn[i+0] + 8192) >> 14;
+ p[0*8 + col] = t;
+ p[1*8 + col] = t;
+ p[2*8 + col] = t;
+ p[3*8 + col] = t;
+ p[4*8 + col] = t;
+ p[5*8 + col] = t;
+ p[6*8 + col] = t;
+ p[7*8 + col] = t;
+ continue;
+ }
+
+ // stage 4
+ v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12;
+ v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12;
+ v2 = p[2*8 + col];
+ v3 = p[6*8 + col];
+ v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12;
+ v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12;
+ v5 = p[3*8 + col];
+ v6 = p[5*8 + col];
+
+ // stage 3
+ t = (v0 - v1 + 1) >> 1;
+ v0 = (v0 + v1 + 1) >> 1;
+ v1 = t;
+ t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
+ v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
+ v3 = t;
+ t = (v4 - v6 + 1) >> 1;
+ v4 = (v4 + v6 + 1) >> 1;
+ v6 = t;
+ t = (v7 + v5 + 1) >> 1;
+ v5 = (v7 - v5 + 1) >> 1;
+ v7 = t;
+
+ // stage 2
+ t = (v0 - v3 + 1) >> 1;
+ v0 = (v0 + v3 + 1) >> 1;
+ v3 = t;
+ t = (v1 - v2 + 1) >> 1;
+ v1 = (v1 + v2 + 1) >> 1;
+ v2 = t;
+ t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+ v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+ v7 = t;
+ t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+ v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+ v6 = t;
+
+ // stage 1
+ p[0*8 + col] = v0 + v7;
+ p[7*8 + col] = v0 - v7;
+ p[1*8 + col] = v1 + v6;
+ p[6*8 + col] = v1 - v6;
+ p[2*8 + col] = v2 + v5;
+ p[5*8 + col] = v2 - v5;
+ p[3*8 + col] = v3 + v4;
+ p[4*8 + col] = v3 - v4;
+ }
+
+ // convert to 8-bit integers
+ for (i = 0; i < 64; ++i) {
+ final int sample = 128 + ((p[i] + 8) >> 4);
+ dataOut[i] = (byte) ( sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample );
+ }
+ }
+ }
+
+ static interface DecoderFunction {
+ void decode(ComponentIn component, int[] zz) throws IOException;
+ }
+
+ class Decoder {
+ // private int precision;
+ // private int samplesPerLine;
+ // private int scanLines;
+ private int mcusPerLine;
+ private boolean progressive;
+ // private int maxH, maxV;
+ private int spectralStart, spectralEnd;
+ private int successive;
+ private int eobrun;
+ private int successiveACState, successiveACNextValue;
+
+ private int decodeScan(final Frame frame, final ArrayList<ComponentIn> components, int resetInterval,
+ final int spectralStart, final int spectralEnd, final int successivePrev, final int successive) throws IOException {
+ // this.precision = frame.precision;
+ // this.samplesPerLine = frame.samplesPerLine;
+ // this.scanLines = frame.scanLines;
+ this.mcusPerLine = frame.mcusPerLine;
+ this.progressive = frame.progressive;
+ // this.maxH = frame.maxH;
+ // this.maxV = frame.maxV;
+ bstream.skip( bstream.getBitCount() ); // align to next byte
+ this.spectralStart = spectralStart;
+ this.spectralEnd = spectralEnd;
+ this.successive = successive;
+
+ final int componentsLength = components.size();
+
+ final DecoderFunction decodeFn;
+ if (progressive) {
+ if (spectralStart == 0) {
+ decodeFn = successivePrev == 0 ? decodeDCFirst : decodeDCSuccessive;
+ } else {
+ decodeFn = successivePrev == 0 ? decodeACFirst : decodeACSuccessive;
+ }
+ } else {
+ decodeFn = decodeBaseline;
+ }
+
+ int mcu = 0;
+ int mcuExpected;
+ if (componentsLength == 1) {
+ final ComponentIn c = components.get(0);
+ mcuExpected = c.blocksPerLine * c.blocksPerColumn;
+ } else {
+ mcuExpected = mcusPerLine * frame.mcusPerColumn;
+ }
+ if (0 == resetInterval) {
+ resetInterval = mcuExpected;
+ }
+ if(DEBUG) {
+ System.err.println("JPEG.decodeScan.1 resetInterval "+resetInterval+", mcuExpected "+mcuExpected+", sA "+spectralStart+", sP "+successivePrev+", sE "+spectralEnd+", suc "+successive+", decodeFn "+decodeFn.getClass().getSimpleName());
+ }
+ int marker = 0;
+ while ( /* untilMarker || */ mcu < mcuExpected) {
+ // reset interval stuff
+ for (int i = 0; i < componentsLength; i++) {
+ components.get(i).pred = 0;
+ }
+ eobrun = 0;
+
+ try {
+ if (componentsLength == 1) {
+ final ComponentIn component = components.get(0);
+ for (int n = 0; n < resetInterval; n++) {
+ decodeBlock(component, decodeFn, mcu);
+ mcu++;
+ }
+ } else {
+ for (int n = 0; n < resetInterval; n++) {
+ for (int i = 0; i < componentsLength; i++) {
+ final ComponentIn component = components.get(i);
+ final int h = component.h;
+ final int v = component.v;
+ for (int j = 0; j < v; j++) {
+ for (int k = 0; k < h; k++) {
+ decodeMcu(component, decodeFn, mcu, j, k);
+ }
+ }
+ }
+ mcu++;
+ }
+ }
+ } catch (final MarkerException markerException) {
+ if(DEBUG) { System.err.println("JPEG.decodeScan: Marker exception: "+markerException.getMessage()); markerException.printStackTrace(); }
+ return markerException.getMarker();
+ } catch (final CodecException codecException) {
+ if(DEBUG) { System.err.println("JPEG.decodeScan: Codec exception: "+codecException.getMessage()); codecException.printStackTrace(); }
+ bstream.skip( bstream.getBitCount() ); // align to next byte
+ return M_EOI; // force end !
+ }
+
+ // find marker
+ bstream.skip( bstream.getBitCount() ); // align to next byte
+ bstream.mark(2);
+ marker = readUInt16();
+ if( marker < 0xFF00 ) {
+ bstream.reset();
+ throw new CodecException("marker not found @ mcu "+mcu+"/"+mcuExpected+", u16: "+toHexString(marker));
+ }
+ final boolean isRSTx = 0xFFD0 <= marker && marker <= 0xFFD7; // !RSTx
+ if(DEBUG) {
+ System.err.println("JPEG.decodeScan: MCUs "+mcu+"/"+mcuExpected+", u16 "+toHexString(marker)+", RSTx "+isRSTx+", "+frame);
+ }
+ if ( !isRSTx ) {
+ break; // handle !RSTx marker in caller
+ }
+ }
+ return marker;
+ }
+
+ private final int readBit() throws MarkerException, IOException {
+ final int bit = bstream.readBit(true /* msbFirst */);
+ if( Bitstream.EOS == bit || 7 != bstream.getBitCount() ) {
+ return bit;
+ }
+ // new byte read, i.e. bitCount == 7
+ final int bitsData = bstream.getBitBuffer(); // peek for marker
+ if ( 0xFF == bitsData ) { // marker prefix
+ final int nextByte = bstream.getStream().read(); // snoop marker signature, will be dropped!
+ if( -1 == nextByte ) {
+ throw new CodecException("marked prefix 0xFF, then EOF");
+ }
+ if (0 != nextByte) {
+ final int marker = (bitsData << 8) | nextByte;
+ throw new MarkerException(marker, "Marker at readBit pos " + bstream);
+ }
+ // unstuff 0
+ }
+ return bit;
+ }
+
+ private int decodeHuffman(final BinObj tree) throws IOException {
+ BinObj node = tree;
+ int bit;
+ while ( ( bit = readBit() ) != -1 ) {
+ node = node.get(bit);
+ if ( node.isValue ) {
+ return 0x000000FF & node.getValue();
+ }
+ }
+ throw new CodecException("EOF reached at "+bstream);
+ }
+ private int receive(int length) throws IOException {
+ int n = 0;
+ while (length > 0) {
+ final int bit = readBit();
+ if (bit == -1) {
+ return -1;
+ }
+ n = (n << 1) | bit;
+ length--;
+ }
+ return n;
+ }
+ private int receiveAndExtend(final int length) throws IOException {
+ final int n = receive(length);
+ if (n >= 1 << (length - 1)) {
+ return n;
+ }
+ return n + (-1 << length) + 1;
+ }
+
+ final DecoderFunction decodeBaseline = new BaselineDecoder();
+ final DecoderFunction decodeDCFirst = new DCFirstDecoder();
+ final DecoderFunction decodeDCSuccessive = new DCSuccessiveDecoder();
+ final DecoderFunction decodeACFirst = new ACFirstDecoder();
+ final DecoderFunction decodeACSuccessive = new ACSuccessiveDecoder();
+
+ class BaselineDecoder implements DecoderFunction {
+ @Override
+ public void decode(final ComponentIn component, final int[] zz) throws IOException {
+ final int t = decodeHuffman(component.huffmanTableDC);
+ final int diff = ( t == 0 ) ? 0 : receiveAndExtend(t);
+ zz[0] = ( component.pred += diff );
+ int k = 1;
+ while (k < 64) {
+ final int rs = decodeHuffman(component.huffmanTableAC);
+ final int s = rs & 15, r = rs >> 4;
+ if (s == 0) {
+ if (r < 15) {
+ break;
+ }
+ k += 16;
+ continue;
+ }
+ k += r;
+ final int z = dctZigZag[k];
+ zz[z] = receiveAndExtend(s);
+ k++;
+ }
+ }
+ }
+ class DCFirstDecoder implements DecoderFunction {
+ @Override
+ public void decode(final ComponentIn component, final int[] zz) throws IOException {
+ final int t = decodeHuffman(component.huffmanTableDC);
+ final int diff = ( t == 0 ) ? 0 : (receiveAndExtend(t) << successive);
+ zz[0] = ( component.pred += diff );
+ }
+ }
+ class DCSuccessiveDecoder implements DecoderFunction {
+ @Override
+ public void decode(final ComponentIn component, final int[] zz) throws IOException {
+ zz[0] |= readBit() << successive;
+ }
+ }
+
+ class ACFirstDecoder implements DecoderFunction {
+ @Override
+ public void decode(final ComponentIn component, final int[] zz) throws IOException {
+ if (eobrun > 0) {
+ eobrun--;
+ return;
+ }
+ int k = spectralStart;
+ final int e = spectralEnd;
+ while (k <= e) {
+ final int rs = decodeHuffman(component.huffmanTableAC);
+ final int s = rs & 15, r = rs >> 4;
+ if (s == 0) {
+ if (r < 15) {
+ eobrun = receive(r) + (1 << r) - 1;
+ break;
+ }
+ k += 16;
+ continue;
+ }
+ k += r;
+ final int z = dctZigZag[k];
+ zz[z] = receiveAndExtend(s) * (1 << successive);
+ k++;
+ }
+ }
+ }
+ class ACSuccessiveDecoder implements DecoderFunction {
+ @Override
+ public void decode(final ComponentIn component, final int[] zz) throws IOException {
+ int k = spectralStart;
+ final int e = spectralEnd;
+ int r = 0;
+ while (k <= e) {
+ final int z = dctZigZag[k];
+ switch (successiveACState) {
+ case 0: // initial state
+ final int rs = decodeHuffman(component.huffmanTableAC);
+ final int s = rs & 15;
+ r = rs >> 4;
+ if (s == 0) {
+ if (r < 15) {
+ eobrun = receive(r) + (1 << r);
+ successiveACState = 4;
+ } else {
+ r = 16;
+ successiveACState = 1;
+ }
+ } else {
+ // if (s !== 1) {
+ if (s != 1) {
+ throw new CodecException("invalid ACn encoding");
+ }
+ successiveACNextValue = receiveAndExtend(s);
+ successiveACState = r != 0 ? 2 : 3;
+ }
+ continue;
+ case 1: // skipping r zero items
+ case 2:
+ if ( zz[z] != 0 ) {
+ zz[z] += (readBit() << successive);
+ } else {
+ r--;
+ if (r == 0) {
+ successiveACState = successiveACState == 2 ? 3 : 0;
+ }
+ }
+ break;
+ case 3: // set value for a zero item
+ if ( zz[z] != 0 ) {
+ zz[z] += (readBit() << successive);
+ } else {
+ zz[z] = successiveACNextValue << successive;
+ successiveACState = 0;
+ }
+ break;
+ case 4: // eob
+ if ( zz[z] != 0 ) {
+ zz[z] += (readBit() << successive);
+ }
+ break;
+ }
+ k++;
+ }
+ if (successiveACState == 4) {
+ eobrun--;
+ if (eobrun == 0) {
+ successiveACState = 0;
+ }
+ }
+ }
+ }
+ void decodeMcu(final ComponentIn component, final DecoderFunction decoder, final int mcu, final int row, final int col) throws IOException {
+ final int mcuRow = (mcu / mcusPerLine) | 0;
+ final int mcuCol = mcu % mcusPerLine;
+ final int blockRow = mcuRow * component.v + row;
+ final int blockCol = mcuCol * component.h + col;
+ decoder.decode(component, component.getBlock(blockRow, blockCol));
+ }
+ void decodeBlock(final ComponentIn component, final DecoderFunction decoder, final int mcu) throws IOException {
+ final int blockRow = (mcu / component.blocksPerLine) | 0;
+ final int blockCol = mcu % component.blocksPerLine;
+ decoder.decode(component, component.getBlock(blockRow, blockCol));
+ }
+ }
+
+ private final Decoder decoder = new Decoder();
+
+ /** wrong color space ..
+ private final void storeYCbCr2BGR(final PixelStorage pixelStorage, int x, int y, int Y, final int Cb, final int Cr)
+ {
+ if(Y<0) Y=0;
+ int B = Y + ( ( 116130 * Cb ) >> 16 ) ;
+ if(B<0) B=0;
+ else if(B>255) B=255;
+
+ int G = Y - ( ( 22554 * Cb + 46802 * Cr ) >> 16 ) ;
+ if(G<0) G=0;
+ else if(G>255) G=255;
+
+ int R = Y + ( ( 91881 * Cr ) >> 16 );
+ if(R<0) R=0;
+ else if(R>255) R=255;
+
+ pixelStorage.storeRGB(x, y, (byte)R, (byte)G, (byte)B);
+ } */
+
+ public synchronized void getPixel(final JPEGDecoder.ColorSink pixelStorage, final int width, final int height) {
+ final int scaleX = this.width / width, scaleY = this.height / height;
+
+ final int componentCount = this.components.length;
+ final ColorSpace sourceCS = ( null != adobe ) ? adobe.colorSpace : ColorSpace.YCbCr;
+ final ColorSpace storageCS = pixelStorage.allocate(width, height, sourceCS, componentCount);
+ if( ColorSpace.RGB != storageCS && ColorSpace.YCbCr != storageCS ) {
+ throw new IllegalArgumentException("Unsupported storage color space: "+storageCS);
+ }
+
+ switch (componentCount) {
+ case 1: {
+ // Grayscale
+ final ComponentOut component1 = this.components[0];
+ for (int y = 0; y < height; y++) {
+ final byte[] component1Line = component1.getLine((int)(y * component1.scaleY * scaleY));
+ for (int x = 0; x < width; x++) {
+ final byte Y = component1Line[(int)(x * component1.scaleX * scaleX)];
+ if( ColorSpace.YCbCr == storageCS ) {
+ pixelStorage.storeYCbCr(x, y, Y, (byte)0, (byte)0);
+ } else {
+ pixelStorage.storeRGB(x, y, Y, Y, Y);
+ }
+ }
+ }
+ }
+ break;
+ case 2: {
+ // PDF might compress two component data in custom colorspace
+ final ComponentOut component1 = this.components[0];
+ final ComponentOut component2 = this.components[1];
+ for (int y = 0; y < height; y++) {
+ final int ys = y * scaleY;
+ final byte[] component1Line = component1.getLine((int)(ys * component1.scaleY));
+ final byte[] component2Line = component1.getLine((int)(ys * component2.scaleY));
+ for (int x = 0; x < width; x++) {
+ final int xs = x * scaleX;
+ final byte Y1 = component1Line[(int)(xs * component1.scaleX)];
+ final byte Y2 = component2Line[(int)(xs * component2.scaleX)];
+ pixelStorage.store2(x, y, Y1, Y2);
+ }
+ }
+ }
+ break;
+ case 3: {
+ if (ColorSpace.YCbCr != sourceCS) {
+ throw new CodecException("Unsupported source color space w 3 components: "+sourceCS);
+ }
+ final ComponentOut component1 = this.components[0];
+ final ComponentOut component2 = this.components[1];
+ final ComponentOut component3 = this.components[2];
+ for (int y = 0; y < height; y++) {
+ final int ys = y * scaleY;
+ final byte[] component1Line = component1.getLine((int)(ys * component1.scaleY));
+ final byte[] component2Line = component2.getLine((int)(ys * component2.scaleY));
+ final byte[] component3Line = component3.getLine((int)(ys * component3.scaleY));
+ if( ColorSpace.YCbCr == storageCS ) {
+ for (int x = 0; x < width; x++) {
+ final int xs = x * scaleX;
+ final byte Y = component1Line[(int)(xs * component1.scaleX)];
+ final byte Cb = component2Line[(int)(xs * component2.scaleX)];
+ final byte Cr = component3Line[(int)(xs * component3.scaleX)];
+ pixelStorage.storeYCbCr(x, y, Y, Cb, Cr);
+ }
+ } else {
+ for (int x = 0; x < width; x++) {
+ final int xs = x * scaleX;
+ final int Y = 0x000000FF & component1Line[(int)(xs * component1.scaleX)];
+ final int Cb = 0x000000FF & component2Line[(int)(xs * component2.scaleX)];
+ final int Cr = 0x000000FF & component3Line[(int)(xs * component3.scaleX)];
+ // storeYCbCr2BGR(pixelStorage, x, y, Y, Cb, Cr);
+ final byte R = clampTo8bit(Y + 1.402f * (Cr - 128f));
+ final byte G = clampTo8bit(Y - 0.3441363f * (Cb - 128f) - 0.71413636f * (Cr - 128f));
+ final byte B = clampTo8bit(Y + 1.772f * (Cb - 128f));
+ pixelStorage.storeRGB(x, y, R, G, B);
+ }
+ }
+ }
+ }
+ break;
+ case 4: {
+ if (ColorSpace.YCCK != sourceCS && ColorSpace.CMYK != sourceCS) {
+ throw new CodecException("Unsupported source color space w 4 components: "+sourceCS);
+ }
+ final ComponentOut component1 = this.components[0];
+ final ComponentOut component2 = this.components[1];
+ final ComponentOut component3 = this.components[2];
+ final ComponentOut component4 = this.components[3];
+ for (int y = 0; y < height; y++) {
+ final int ys = y * scaleY;
+ final byte[] component1Line = component1.getLine((int)(ys * component1.scaleY));
+ final byte[] component2Line = component2.getLine((int)(ys * component2.scaleY));
+ final byte[] component3Line = component3.getLine((int)(ys * component3.scaleY));
+ final byte[] component4Line = component4.getLine((int)(ys * component4.scaleY));
+ if( ColorSpace.YCbCr == storageCS ) {
+ if (ColorSpace.YCCK != sourceCS) {
+ throw new CodecException("Unsupported storage color space "+storageCS+" with source color space "+sourceCS);
+ }
+ for (int x = 0; x < width; x++) {
+ final int xs = x * scaleX;
+ final byte Y1 = component1Line[(int)(xs * component1.scaleX)];
+ final byte C1 = component2Line[(int)(xs * component2.scaleX)];
+ final byte C2 = component3Line[(int)(xs * component3.scaleX)];
+ // final byte K = component4Line[(int)(xs * component4.scaleX)];
+ // FIXME: YCCK is not really YCbCr, since K (black) is missing!
+ pixelStorage.storeYCbCr(x, y, Y1, C1, C2);
+ }
+ } else {
+ if (ColorSpace.CMYK == sourceCS) {
+ for (int x = 0; x < width; x++) {
+ final int xs = x * scaleX;
+ final int cC = 0x000000FF & component1Line[(int)(xs * component1.scaleX)];
+ final int cM = 0x000000FF & component2Line[(int)(xs * component2.scaleX)];
+ final int cY = 0x000000FF & component3Line[(int)(xs * component3.scaleX)];
+ final int cK = 0x000000FF & component4Line[(int)(xs * component4.scaleX)];
+ // CMYK -> RGB
+ final byte R = clampTo8bit( ( cC * cK ) / 255f );
+ final byte G = clampTo8bit( ( cM * cK ) / 255f );
+ final byte B = clampTo8bit( ( cY * cK ) / 255f );
+ pixelStorage.storeRGB(x, y, R, G, B);
+ }
+ } else { // ColorModel.YCCK == sourceCM
+ for (int x = 0; x < width; x++) {
+ final int xs = x * scaleX;
+ final int Y = 0x000000FF & component1Line[(int)(xs * component1.scaleX)];
+ final int Cb = 0x000000FF & component2Line[(int)(xs * component2.scaleX)];
+ final int Cr = 0x000000FF & component3Line[(int)(xs * component3.scaleX)];
+ final int cK = 0x000000FF & component4Line[(int)(xs * component4.scaleX)];
+ // YCCK -> 255f - [ R'G'B' ] -> CMYK
+ final float cC = 255f - ( Y + 1.402f * (Cr - 128f) );
+ final float cM = 255f - ( Y - 0.3441363f * (Cb - 128f) - 0.71413636f * (Cr - 128f) );
+ final float cY = 255f - ( Y + 1.772f * (Cb - 128f) );
+ // CMYK -> RGB
+ final byte R = clampTo8bit( ( cC * cK ) / 255f );
+ final byte G = clampTo8bit( ( cM * cK ) / 255f );
+ final byte B = clampTo8bit( ( cY * cK ) / 255f );
+ pixelStorage.storeRGB(x, y, R, G, B);
+ }
+ }
+ }
+ }
+ }
+ break;
+ default:
+ throw new CodecException("Unsupported color model: Space "+sourceCS+", components "+componentCount);
+ }
+ }
+
+ private static byte clampTo8bit(final float a) {
+ return (byte) ( a < 0f ? 0 : a > 255f ? 255 : a );
+ }
+
+ private static String toHexString(final int v) {
+ return "0x"+Integer.toHexString(v);
+ }
+} \ No newline at end of file
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/FilterType.java b/src/jogl/classes/jogamp/opengl/util/pngj/FilterType.java
index a34f73ab2..c577c4fd5 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/FilterType.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/FilterType.java
@@ -1,8 +1,10 @@
package jogamp.opengl.util.pngj;
+import java.util.HashMap;
+
/**
* Internal PNG predictor filter, or strategy to select it.
- *
+ *
*/
public enum FilterType {
/**
@@ -26,69 +28,46 @@ public enum FilterType {
*/
FILTER_PAETH(4),
/**
- * Default strategy: select one of the above filters depending on global image parameters
+ * Default strategy: select one of the above filters depending on global
+ * image parameters
*/
FILTER_DEFAULT(-1),
/**
- * Aggresive strategy: select one of the above filters trying each of the filters (this is done every 8 rows)
+ * Aggressive strategy: select one of the above filters trying each of the
+ * filters (every 8 rows)
*/
FILTER_AGGRESSIVE(-2),
/**
+ * Very aggressive strategy: select one of the above filters trying each of
+ * the filters (for every row!)
+ */
+ FILTER_VERYAGGRESSIVE(-3),
+ /**
* Uses all fiters, one for lines, cyciclally. Only for tests.
*/
- FILTER_ALTERNATE(-3),
+ FILTER_CYCLIC(-50),
+
/**
- * Aggresive strategy: select one of the above filters trying each of the filters (this is done for every row!)
+ * Not specified, placeholder for unknown or NA filters.
*/
- FILTER_VERYAGGRESSIVE(-4), ;
+ FILTER_UNKNOWN(-100), ;
public final int val;
- private FilterType(int val) {
+ private FilterType(final int val) {
this.val = val;
}
- public static FilterType getByVal(int i) {
- for (FilterType ft : values()) {
- if (ft.val == i)
- return ft;
- }
- return null;
- }
-
- public static int unfilterRowNone(int r) {
- return (int) (r & 0xFF);
- }
-
- public static int unfilterRowSub(int r, int left) {
- return ((int) (r + left) & 0xFF);
- }
+ private static HashMap<Integer, FilterType> byVal;
- public static int unfilterRowUp(int r, int up) {
- return ((int) (r + up) & 0xFF);
- }
-
- public static int unfilterRowAverage(int r, int left, int up) {
- return (r + (left + up) / 2) & 0xFF;
+ static {
+ byVal = new HashMap<Integer, FilterType>();
+ for (final FilterType ft : values()) {
+ byVal.put(ft.val, ft);
+ }
}
- public static int unfilterRowPaeth(int r, int a, int b, int c) { // a = left, b = above, c = upper left
- return (r + filterPaethPredictor(a, b, c)) & 0xFF;
+ public static FilterType getByVal(final int i) {
+ return byVal.get(i);
}
- public static int filterPaethPredictor(int a, int b, int c) {
- // from http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html
- // a = left, b = above, c = upper left
- final int p = a + b - c;// ; initial estimate
- final int pa = p >= a ? p - a : a - p;
- final int pb = p >= b ? p - b : b - p;
- final int pc = p >= c ? p - c : c - p;
- // ; return nearest of a,b,c,
- // ; breaking ties in order a,b,c.
- if (pa <= pb && pa <= pc)
- return a;
- else if (pb <= pc)
- return b;
- else
- return c;
- }
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/FilterWriteStrategy.java b/src/jogl/classes/jogamp/opengl/util/pngj/FilterWriteStrategy.java
index 27586b292..63f456347 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/FilterWriteStrategy.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/FilterWriteStrategy.java
@@ -1,7 +1,7 @@
package jogamp.opengl.util.pngj;
/**
- * Manages the writer strategy for selecting the internal png "filter"
+ * Manages the writer strategy for selecting the internal png predictor filter
*/
class FilterWriteStrategy {
private static final int COMPUTE_STATS_EVERY_N_LINES = 8;
@@ -11,15 +11,15 @@ class FilterWriteStrategy {
private FilterType currentType; // 0-4
private int lastRowTested = -1000000;
// performance of each filter (less is better) (can be negative)
- private double[] lastSums = new double[5];
+ private final double[] lastSums = new double[5];
// performance of each filter (less is better) (can be negative)
- private double[] lastEntropies = new double[5];
+ private final double[] lastEntropies = new double[5];
// a priori preference (NONE SUB UP AVERAGE PAETH)
private double[] preference = new double[] { 1.1, 1.1, 1.1, 1.1, 1.2 };
private int discoverEachLines = -1;
- private double[] histogram1 = new double[256];
+ private final double[] histogram1 = new double[256];
- FilterWriteStrategy(ImageInfo imgInfo, FilterType configuredType) {
+ FilterWriteStrategy(final ImageInfo imgInfo, final FilterType configuredType) {
this.imgInfo = imgInfo;
this.configuredType = configuredType;
if (configuredType.val < 0) { // first guess
@@ -36,7 +36,7 @@ class FilterWriteStrategy {
discoverEachLines = 1;
}
- boolean shouldTestAll(int rown) {
+ boolean shouldTestAll(final int rown) {
if (discoverEachLines > 0 && lastRowTested + discoverEachLines <= rown) {
currentType = null;
return true;
@@ -44,7 +44,7 @@ class FilterWriteStrategy {
return false;
}
- public void setPreference(double none, double sub, double up, double ave, double paeth) {
+ public void setPreference(final double none, final double sub, final double up, final double ave, final double paeth) {
preference = new double[] { none, sub, up, ave, paeth };
}
@@ -52,7 +52,7 @@ class FilterWriteStrategy {
return (discoverEachLines > 0);
}
- void fillResultsForFilter(int rown, FilterType type, double sum, int[] histo, boolean tentative) {
+ void fillResultsForFilter(final int rown, final FilterType type, final double sum, final int[] histo, final boolean tentative) {
lastRowTested = rown;
lastSums[type.val] = sum;
if (histo != null) {
@@ -72,7 +72,7 @@ class FilterWriteStrategy {
}
}
- FilterType gimmeFilterType(int rown, boolean useEntropy) {
+ FilterType gimmeFilterType(final int rown, final boolean useEntropy) {
if (currentType == null) { // get better
if (rown == 0)
currentType = FilterType.FILTER_SUB;
@@ -89,7 +89,7 @@ class FilterWriteStrategy {
}
}
}
- if (configuredType == FilterType.FILTER_ALTERNATE) {
+ if (configuredType == FilterType.FILTER_CYCLIC) {
currentType = FilterType.getByVal((currentType.val + 1) % 5);
}
return currentType;
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/ImageInfo.java b/src/jogl/classes/jogamp/opengl/util/pngj/ImageInfo.java
index 2f6b89e9c..cdd17a291 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/ImageInfo.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/ImageInfo.java
@@ -3,7 +3,8 @@ package jogamp.opengl.util.pngj;
/**
* Simple immutable wrapper for basic image info.
* <p>
- * Some parameters are redundant, but the constructor receives an 'orthogonal' subset.
+ * Some parameters are redundant, but the constructor receives an 'orthogonal'
+ * subset.
* <p>
* ref: http://www.w3.org/TR/PNG/#11IHDR
*/
@@ -13,24 +14,25 @@ public class ImageInfo {
private static final int MAX_COLS_ROWS_VAL = 1000000;
/**
- * Image width, in pixels.
+ * Cols= Image width, in pixels.
*/
public final int cols;
/**
- * Image height, in pixels
+ * Rows= Image height, in pixels
*/
public final int rows;
/**
- * Bits per sample (per channel) in the buffer (1-2-4-8-16). This is 8-16 for RGB/ARGB images, 1-2-4-8 for
- * grayscale. For indexed images, number of bits per palette index (1-2-4-8)
+ * Bits per sample (per channel) in the buffer (1-2-4-8-16). This is 8-16
+ * for RGB/ARGB images, 1-2-4-8 for grayscale. For indexed images, number of
+ * bits per palette index (1-2-4-8)
*/
public final int bitDepth;
/**
- * Number of channels, as used internally. This is 3 for RGB, 4 for RGBA, 2 for GA (gray with alpha), 1 for
- * grayscales or indexed.
+ * Number of channels, as used internally: 3 for RGB, 4 for RGBA, 2 for GA
+ * (gray with alpha), 1 for grayscale or indexed.
*/
public final int channels;
@@ -50,7 +52,8 @@ public class ImageInfo {
public final boolean indexed;
/**
- * Flag: true if image internally uses less than one byte per sample (bit depth 1-2-4)
+ * Flag: true if image internally uses less than one byte per sample (bit
+ * depth 1-2-4)
*/
public final boolean packed;
@@ -75,27 +78,34 @@ public class ImageInfo {
public final int samplesPerRow;
/**
- * For internal use only. Samples available for our packed scanline. Equals samplesPerRow if not packed. Elsewhere,
- * it's lower
+ * Amount of "packed samples" : when several samples are stored in a single
+ * byte (bitdepth 1,2 4) they are counted as one "packed sample". This is
+ * less that samplesPerRow only when bitdepth is 1-2-4 (flag packed = true)
+ * <p>
+ * This equals the number of elements in the scanline array if working with
+ * packedMode=true
+ * <p>
+ * For internal use, client code should rarely access this.
*/
- final int samplesPerRowP;
+ public final int samplesPerRowPacked;
/**
* Short constructor: assumes truecolor (RGB/RGBA)
*/
- public ImageInfo(int cols, int rows, int bitdepth, boolean alpha) {
+ public ImageInfo(final int cols, final int rows, final int bitdepth, final boolean alpha) {
this(cols, rows, bitdepth, alpha, false, false);
}
/**
* Full constructor
- *
+ *
* @param cols
* Width in pixels
* @param rows
* Height in pixels
* @param bitdepth
- * Bits per sample, in the buffer : 8-16 for RGB true color and greyscale
+ * Bits per sample, in the buffer : 8-16 for RGB true color and
+ * greyscale
* @param alpha
* Flag: has an alpha channel (RGBA or GA)
* @param grayscale
@@ -103,7 +113,7 @@ public class ImageInfo {
* @param indexed
* Flag: has palette
*/
- public ImageInfo(int cols, int rows, int bitdepth, boolean alpha, boolean grayscale, boolean indexed) {
+ public ImageInfo(final int cols, final int rows, final int bitdepth, final boolean alpha, final boolean grayscale, final boolean indexed) {
this.cols = cols;
this.rows = rows;
this.alpha = alpha;
@@ -119,7 +129,7 @@ public class ImageInfo {
this.bytesPixel = (bitspPixel + 7) / 8;
this.bytesPerRow = (bitspPixel * cols + 7) / 8;
this.samplesPerRow = channels * this.cols;
- this.samplesPerRowP = packed ? bytesPerRow : samplesPerRow;
+ this.samplesPerRowPacked = packed ? bytesPerRow : samplesPerRow;
// several checks
switch (this.bitDepth) {
case 1:
@@ -147,7 +157,7 @@ public class ImageInfo {
public String toString() {
return "ImageInfo [cols=" + cols + ", rows=" + rows + ", bitDepth=" + bitDepth + ", channels=" + channels
+ ", bitspPixel=" + bitspPixel + ", bytesPixel=" + bytesPixel + ", bytesPerRow=" + bytesPerRow
- + ", samplesPerRow=" + samplesPerRow + ", samplesPerRowP=" + samplesPerRowP + ", alpha=" + alpha
+ + ", samplesPerRow=" + samplesPerRow + ", samplesPerRowP=" + samplesPerRowPacked + ", alpha=" + alpha
+ ", greyscale=" + greyscale + ", indexed=" + indexed + ", packed=" + packed + "]";
}
@@ -157,38 +167,27 @@ public class ImageInfo {
int result = 1;
result = prime * result + (alpha ? 1231 : 1237);
result = prime * result + bitDepth;
- result = prime * result + bitspPixel;
- result = prime * result + bytesPerRow;
- result = prime * result + bytesPixel;
result = prime * result + channels;
result = prime * result + cols;
result = prime * result + (greyscale ? 1231 : 1237);
result = prime * result + (indexed ? 1231 : 1237);
- result = prime * result + (packed ? 1231 : 1237);
result = prime * result + rows;
- result = prime * result + samplesPerRow;
return result;
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
- ImageInfo other = (ImageInfo) obj;
+ final ImageInfo other = (ImageInfo) obj;
if (alpha != other.alpha)
return false;
if (bitDepth != other.bitDepth)
return false;
- if (bitspPixel != other.bitspPixel)
- return false;
- if (bytesPerRow != other.bytesPerRow)
- return false;
- if (bytesPixel != other.bytesPixel)
- return false;
if (channels != other.channels)
return false;
if (cols != other.cols)
@@ -197,12 +196,9 @@ public class ImageInfo {
return false;
if (indexed != other.indexed)
return false;
- if (packed != other.packed)
- return false;
if (rows != other.rows)
return false;
- if (samplesPerRow != other.samplesPerRow)
- return false;
return true;
}
+
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/ImageLine.java b/src/jogl/classes/jogamp/opengl/util/pngj/ImageLine.java
index bfbb35b7c..5ad2e4409 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/ImageLine.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/ImageLine.java
@@ -1,11 +1,12 @@
package jogamp.opengl.util.pngj;
-import java.util.Arrays;
+import jogamp.opengl.util.pngj.ImageLineHelper.ImageLineStats;
/**
* Lightweight wrapper for an image scanline, used for read and write.
* <p>
- * This object can be (usually it is) reused while iterating over the image lines.
+ * This object can be (usually it is) reused while iterating over the image
+ * lines.
* <p>
* See <code>scanline</code> field, to understand the format.
*/
@@ -18,28 +19,97 @@ public class ImageLine {
private int rown = 0;
/**
- * The 'scanline' is an array of integers, corresponds to an image line (row).
+ * The 'scanline' is an array of integers, corresponds to an image line
+ * (row).
* <p>
- * Except for 'packed' formats (gray/indexed with 1-2-4 bitdepth) each int is a "sample" (one for channel), (0-255
- * or 0-65535) in the respective PNG sequence sequence : (R G B R G B...) or (R G B A R G B A...) or (g g g ...) or
- * ( i i i) (palette index)
+ * Except for 'packed' formats (gray/indexed with 1-2-4 bitdepth) each
+ * <code>int</code> is a "sample" (one for channel), (0-255 or 0-65535) in
+ * the corresponding PNG sequence: <code>R G B R G B...</code> or
+ * <code>R G B A R G B A...</tt>
+ * or <code>g g g ...</code> or <code>i i i</code> (palette index)
* <p>
- * For bitdepth 1/2/4 , each element is a PACKED byte! To get an unpacked copy, see <code>tf_pack()</code> and its
- * inverse <code>tf_unpack()</code>
+ * For bitdepth=1/2/4 , and if samplesUnpacked=false, each value is a PACKED
+ * byte!
* <p>
- * To convert a indexed line to RGB balues, see <code>ImageLineHelper.tf_palIdx2RGB()</code> (can't do the reverse)
+ * To convert a indexed line to RGB balues, see
+ * <code>ImageLineHelper.palIdx2RGB()</code> (you can't do the reverse)
*/
- public final int[] scanline; // see explanation above!!
+ public final int[] scanline;
+ /**
+ * Same as {@link #scanline}, but with one byte per sample. Only one of
+ * scanline and scanlineb is valid - this depends on {@link #sampleType}
+ */
+ public final byte[] scanlineb;
protected FilterType filterUsed; // informational ; only filled by the reader
- public final int channels; // copied from imgInfo, more handy
- public final int bitDepth; // copied from imgInfo, more handy
+ final int channels; // copied from imgInfo, more handy
+ final int bitDepth; // copied from imgInfo, more handy
+ final int elementsPerRow; // = imgInfo.samplePerRowPacked, if packed:imgInfo.samplePerRow elswhere
+
+ public enum SampleType {
+ INT, // 4 bytes per sample
+ // SHORT, // 2 bytes per sample
+ BYTE // 1 byte per sample
+ }
+
+ /**
+ * tells if we are using BYTE or INT to store the samples.
+ */
+ public final SampleType sampleType;
+
+ /**
+ * true: each element of the scanline array represents a sample always, even
+ * for internally packed PNG formats
+ *
+ * false: if the original image was of packed type (bit depth less than 8)
+ * we keep samples packed in a single array element
+ */
+ public final boolean samplesUnpacked;
+
+ /**
+ * default mode: INT packed
+ */
+ public ImageLine(final ImageInfo imgInfo) {
+ this(imgInfo, SampleType.INT, false);
+ }
+
+ /**
+ *
+ * @param imgInfo
+ * Inmutable ImageInfo, basic parameter of the image we are
+ * reading or writing
+ * @param stype
+ * INT or BYTE : this determines which scanline is the really
+ * used one
+ * @param unpackedMode
+ * If true, we use unpacked format, even for packed original
+ * images
+ *
+ */
+ public ImageLine(final ImageInfo imgInfo, final SampleType stype, final boolean unpackedMode) {
+ this(imgInfo, stype, unpackedMode, null, null);
+ }
- public ImageLine(ImageInfo imgInfo) {
+ /**
+ * If a preallocated array is passed, the copy is shallow
+ */
+ ImageLine(final ImageInfo imgInfo, final SampleType stype, final boolean unpackedMode, final int[] sci, final byte[] scb) {
this.imgInfo = imgInfo;
channels = imgInfo.channels;
- scanline = new int[imgInfo.samplesPerRowP];
- this.bitDepth = imgInfo.bitDepth;
+ bitDepth = imgInfo.bitDepth;
+ filterUsed = FilterType.FILTER_UNKNOWN;
+ this.sampleType = stype;
+ this.samplesUnpacked = unpackedMode || !imgInfo.packed;
+ elementsPerRow = this.samplesUnpacked ? imgInfo.samplesPerRow : imgInfo.samplesPerRowPacked;
+ if (stype == SampleType.INT) {
+ scanline = sci != null ? sci : new int[elementsPerRow];
+ scanlineb = null;
+ } else if (stype == SampleType.BYTE) {
+ scanlineb = scb != null ? scb : new byte[elementsPerRow];
+ scanline = null;
+ } else
+ throw new PngjExceptionInternal("bad ImageLine initialization");
+ this.rown = -1;
}
/** This row number inside the image (0 is top) */
@@ -47,129 +117,217 @@ public class ImageLine {
return rown;
}
- /** Increments row number */
- public void incRown() {
- this.rown++;
- }
-
- /** Sets row number */
- public void setRown(int n) {
+ /** Sets row number (0 : Rows-1) */
+ public void setRown(final int n) {
this.rown = n;
}
- /** Sets scanline, making copy from passed array */
- public void setScanLine(int[] b) {
- System.arraycopy(b, 0, scanline, 0, scanline.length);
+ /*
+ * Unpacks scanline (for bitdepth 1-2-4)
+ *
+ * Arrays must be prealocated. src : samplesPerRowPacked dst : samplesPerRow
+ *
+ * This usually works in place (with src==dst and length=samplesPerRow)!
+ *
+ * If not, you should only call this only when necesary (bitdepth <8)
+ *
+ * If <code>scale==true<code>, it scales the value (just a bit shift) towards 0-255.
+ */
+ static void unpackInplaceInt(final ImageInfo iminfo, final int[] src, final int[] dst, final boolean scale) {
+ final int bitDepth = iminfo.bitDepth;
+ if (bitDepth >= 8)
+ return; // nothing to do
+ final int mask0 = ImageLineHelper.getMaskForPackedFormatsLs(bitDepth);
+ final int scalefactor = 8 - bitDepth;
+ final int offset0 = 8 * iminfo.samplesPerRowPacked - bitDepth * iminfo.samplesPerRow;
+ int mask, offset, v;
+ if (offset0 != 8) {
+ mask = mask0 << offset0;
+ offset = offset0; // how many bits to shift the mask to the right to recover mask0
+ } else {
+ mask = mask0;
+ offset = 0;
+ }
+ for (int j = iminfo.samplesPerRow - 1, i = iminfo.samplesPerRowPacked - 1; j >= 0; j--) {
+ v = (src[i] & mask) >> offset;
+ if (scale)
+ v <<= scalefactor;
+ dst[j] = v;
+ mask <<= bitDepth;
+ offset += bitDepth;
+ if (offset == 8) {
+ mask = mask0;
+ offset = 0;
+ i--;
+ }
+ }
}
- /**
- * Returns a copy from scanline, in byte array.
- *
- * You can (OPTIONALLY) pass an preallocated array to use.
- **/
- public int[] getScanLineCopy(int[] b) {
- if (b == null || b.length < scanline.length)
- b = new int[scanline.length];
- System.arraycopy(scanline, 0, b, 0, scanline.length);
- return b;
+ /*
+ * Unpacks scanline (for bitdepth 1-2-4)
+ *
+ * Arrays must be prealocated. src : samplesPerRow dst : samplesPerRowPacked
+ *
+ * This usually works in place (with src==dst and length=samplesPerRow)! If not, you should only call this only when
+ * necesary (bitdepth <8)
+ *
+ * The trailing elements are trash
+ *
+ *
+ * If <code>scale==true<code>, it scales the value (just a bit shift) towards 0-255.
+ */
+ static void packInplaceInt(final ImageInfo iminfo, final int[] src, final int[] dst, final boolean scaled) {
+ final int bitDepth = iminfo.bitDepth;
+ if (bitDepth >= 8)
+ return; // nothing to do
+ final int mask0 = ImageLineHelper.getMaskForPackedFormatsLs(bitDepth);
+ final int scalefactor = 8 - bitDepth;
+ final int offset0 = 8 - bitDepth;
+ int v, v0;
+ int offset = 8 - bitDepth;
+ v0 = src[0]; // first value is special for in place
+ dst[0] = 0;
+ if (scaled)
+ v0 >>= scalefactor;
+ v0 = ((v0 & mask0) << offset);
+ for (int i = 0, j = 0; j < iminfo.samplesPerRow; j++) {
+ v = src[j];
+ if (scaled)
+ v >>= scalefactor;
+ dst[i] |= ((v & mask0) << offset);
+ offset -= bitDepth;
+ if (offset < 0) {
+ offset = offset0;
+ i++;
+ dst[i] = 0;
+ }
+ }
+ dst[0] |= v0;
}
- /**
- * Unpacks scanline (for bitdepth 1-2-4) into buffer.
- * <p>
- * You can (OPTIONALLY) pass an preallocated array to use.
- * <p>
- * If scale==TRUE scales the value (just a bit shift).
- */
- public int[] tf_unpack(int[] buf, boolean scale) {
- int len = scanline.length;
- if (bitDepth == 1)
- len *= 8;
- else if (bitDepth == 2)
- len *= 4;
- else if (bitDepth == 4)
- len *= 2;
- if (buf == null)
- buf = new int[len];
+ static void unpackInplaceByte(final ImageInfo iminfo, final byte[] src, final byte[] dst, final boolean scale) {
+ final int bitDepth = iminfo.bitDepth;
if (bitDepth >= 8)
- System.arraycopy(scanline, 0, buf, 0, scanline.length);
- else {
- int mask, offset, v;
- int mask0 = getMaskForPackedFormats();
- int offset0 = 8 - bitDepth;
+ return; // nothing to do
+ final int mask0 = ImageLineHelper.getMaskForPackedFormatsLs(bitDepth);
+ final int scalefactor = 8 - bitDepth;
+ final int offset0 = 8 * iminfo.samplesPerRowPacked - bitDepth * iminfo.samplesPerRow;
+ int mask, offset, v;
+ if (offset0 != 8) {
+ mask = mask0 << offset0;
+ offset = offset0; // how many bits to shift the mask to the right to recover mask0
+ } else {
mask = mask0;
- offset = offset0;
- for (int i = 0, j = 0; i < len; i++) {
- v = (scanline[j] & mask) >> offset;
- if (scale)
- v <<= offset0;
- buf[i] = v;
- mask = mask >> bitDepth;
- offset -= bitDepth;
- if (mask == 0) { // new byte in source
- mask = mask0;
- offset = offset0;
- j++;
- }
+ offset = 0;
+ }
+ for (int j = iminfo.samplesPerRow - 1, i = iminfo.samplesPerRowPacked - 1; j >= 0; j--) {
+ v = (src[i] & mask) >> offset;
+ if (scale)
+ v <<= scalefactor;
+ dst[j] = (byte) v;
+ mask <<= bitDepth;
+ offset += bitDepth;
+ if (offset == 8) {
+ mask = mask0;
+ offset = 0;
+ i--;
}
}
- return buf;
}
/**
- * Packs scanline (for bitdepth 1-2-4) from buffer.
- * <p>
- * If scale==TRUE scales the value (just a bit shift).
- */
- public void tf_pack(int[] buf, boolean scale) { // writes scanline
- int len = scanline.length;
- if (bitDepth == 1)
- len *= 8;
- else if (bitDepth == 2)
- len *= 4;
- else if (bitDepth == 4)
- len *= 2;
+ * size original: samplesPerRow sizeFinal: samplesPerRowPacked (trailing
+ * elements are trash!)
+ **/
+ static void packInplaceByte(final ImageInfo iminfo, final byte[] src, final byte[] dst, final boolean scaled) {
+ final int bitDepth = iminfo.bitDepth;
if (bitDepth >= 8)
- System.arraycopy(buf, 0, scanline, 0, scanline.length);
- else {
- int offset0 = 8 - bitDepth;
- int mask0 = getMaskForPackedFormats() >> offset0;
- int offset, v;
- offset = offset0;
- Arrays.fill(scanline, 0);
- for (int i = 0, j = 0; i < len; i++) {
- v = buf[i];
- if (scale)
- v >>= offset0;
- v = (v & mask0) << offset;
- scanline[j] |= v;
- offset -= bitDepth;
- if (offset < 0) { // new byte in scanline
- offset = offset0;
- j++;
- }
+ return; // nothing to do
+ final int mask0 = ImageLineHelper.getMaskForPackedFormatsLs(bitDepth);
+ final int scalefactor = 8 - bitDepth;
+ final int offset0 = 8 - bitDepth;
+ int v, v0;
+ int offset = 8 - bitDepth;
+ v0 = src[0]; // first value is special
+ dst[0] = 0;
+ if (scaled)
+ v0 >>= scalefactor;
+ v0 = ((v0 & mask0) << offset);
+ for (int i = 0, j = 0; j < iminfo.samplesPerRow; j++) {
+ v = src[j];
+ if (scaled)
+ v >>= scalefactor;
+ dst[i] |= ((v & mask0) << offset);
+ offset -= bitDepth;
+ if (offset < 0) {
+ offset = offset0;
+ i++;
+ dst[i] = 0;
}
}
+ dst[0] |= v0;
}
- private int getMaskForPackedFormats() { // Utility function for pacj/unpack
- if (bitDepth == 1)
- return 0x80;
- if (bitDepth == 2)
- return 0xc0;
- if (bitDepth == 4)
- return 0xf0;
- throw new RuntimeException("?");
+ /**
+ * Creates a new ImageLine similar to this, but unpacked
+ *
+ * The caller must be sure that the original was really packed
+ */
+ public ImageLine unpackToNewImageLine() {
+ final ImageLine newline = new ImageLine(imgInfo, sampleType, true);
+ if (sampleType == SampleType.INT)
+ unpackInplaceInt(imgInfo, scanline, newline.scanline, false);
+ else
+ unpackInplaceByte(imgInfo, scanlineb, newline.scanlineb, false);
+ return newline;
+ }
+
+ /**
+ * Creates a new ImageLine similar to this, but packed
+ *
+ * The caller must be sure that the original was really unpacked
+ */
+ public ImageLine packToNewImageLine() {
+ final ImageLine newline = new ImageLine(imgInfo, sampleType, false);
+ if (sampleType == SampleType.INT)
+ packInplaceInt(imgInfo, scanline, newline.scanline, false);
+ else
+ packInplaceByte(imgInfo, scanlineb, newline.scanlineb, false);
+ return newline;
}
public FilterType getFilterUsed() {
return filterUsed;
}
+ public void setFilterUsed(final FilterType ft) {
+ filterUsed = ft;
+ }
+
+ public int[] getScanlineInt() {
+ return scanline;
+ }
+
+ public byte[] getScanlineByte() {
+ return scanlineb;
+ }
+
/**
* Basic info
*/
+ @Override
public String toString() {
return "row=" + rown + " cols=" + imgInfo.cols + " bpc=" + imgInfo.bitDepth + " size=" + scanline.length;
}
+
+ /**
+ * Prints some statistics - just for debugging
+ */
+ public static void showLineInfo(final ImageLine line) {
+ System.out.println(line);
+ final ImageLineStats stats = new ImageLineHelper.ImageLineStats(line);
+ System.out.println(stats);
+ System.out.println(ImageLineHelper.infoFirstLastPixels(line));
+ }
+
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/ImageLineHelper.java b/src/jogl/classes/jogamp/opengl/util/pngj/ImageLineHelper.java
new file mode 100644
index 000000000..616ccd560
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/ImageLineHelper.java
@@ -0,0 +1,329 @@
+package jogamp.opengl.util.pngj;
+
+import jogamp.opengl.util.pngj.ImageLine.SampleType;
+import jogamp.opengl.util.pngj.chunks.PngChunkPLTE;
+import jogamp.opengl.util.pngj.chunks.PngChunkTRNS;
+
+/**
+ * Bunch of utility static methods to process/analyze an image line at the pixel
+ * level.
+ * <p>
+ * Not essential at all, some methods are probably to be removed if future
+ * releases.
+ * <p>
+ * WARNING: most methods for getting/setting values work currently only for
+ * integer base imageLines
+ */
+public class ImageLineHelper {
+
+ private final static double BIG_VALUE = Double.MAX_VALUE * 0.5;
+
+ private final static double BIG_VALUE_NEG = Double.MAX_VALUE * (-0.5);
+
+ /**
+ * Given an indexed line with a palette, unpacks as a RGB array, or RGBA if
+ * a non nul PngChunkTRNS chunk is passed
+ *
+ * @param line
+ * ImageLine as returned from PngReader
+ * @param pal
+ * Palette chunk
+ * @param buf
+ * Preallocated array, optional
+ * @return R G B (A), one sample 0-255 per array element. Ready for
+ * pngw.writeRowInt()
+ */
+ public static int[] palette2rgb(ImageLine line, final PngChunkPLTE pal, final PngChunkTRNS trns, int[] buf) {
+ final boolean isalpha = trns != null;
+ final int channels = isalpha ? 4 : 3;
+ final int nsamples = line.imgInfo.cols * channels;
+ if (buf == null || buf.length < nsamples)
+ buf = new int[nsamples];
+ if (!line.samplesUnpacked)
+ line = line.unpackToNewImageLine();
+ final boolean isbyte = line.sampleType == SampleType.BYTE;
+ final int nindexesWithAlpha = trns != null ? trns.getPalletteAlpha().length : 0;
+ for (int c = 0; c < line.imgInfo.cols; c++) {
+ final int index = isbyte ? (line.scanlineb[c] & 0xFF) : line.scanline[c];
+ pal.getEntryRgb(index, buf, c * channels);
+ if (isalpha) {
+ final int alpha = index < nindexesWithAlpha ? trns.getPalletteAlpha()[index] : 255;
+ buf[c * channels + 3] = alpha;
+ }
+ }
+ return buf;
+ }
+
+ public static int[] palette2rgb(final ImageLine line, final PngChunkPLTE pal, final int[] buf) {
+ return palette2rgb(line, pal, null, buf);
+ }
+
+ /**
+ * what follows is pretty uninteresting/untested/obsolete, subject to change
+ */
+ /**
+ * Just for basic info or debugging. Shows values for first and last pixel.
+ * Does not include alpha
+ */
+ public static String infoFirstLastPixels(final ImageLine line) {
+ return line.imgInfo.channels == 1 ? String.format("first=(%d) last=(%d)", line.scanline[0],
+ line.scanline[line.scanline.length - 1]) : String.format("first=(%d %d %d) last=(%d %d %d)",
+ line.scanline[0], line.scanline[1], line.scanline[2], line.scanline[line.scanline.length
+ - line.imgInfo.channels], line.scanline[line.scanline.length - line.imgInfo.channels + 1],
+ line.scanline[line.scanline.length - line.imgInfo.channels + 2]);
+ }
+
+ public static String infoFull(final ImageLine line) {
+ final ImageLineStats stats = new ImageLineStats(line);
+ return "row=" + line.getRown() + " " + stats.toString() + "\n " + infoFirstLastPixels(line);
+ }
+
+ /**
+ * Computes some statistics for the line. Not very efficient or elegant,
+ * mainly for tests. Only for RGB/RGBA Outputs values as doubles (0.0 - 1.0)
+ */
+ static class ImageLineStats {
+ public double[] prom = { 0.0, 0.0, 0.0, 0.0 }; // channel averages
+ public double[] maxv = { BIG_VALUE_NEG, BIG_VALUE_NEG, BIG_VALUE_NEG, BIG_VALUE_NEG }; // maximo
+ public double[] minv = { BIG_VALUE, BIG_VALUE, BIG_VALUE, BIG_VALUE };
+ public double promlum = 0.0; // maximum global (luminance)
+ public double maxlum = BIG_VALUE_NEG; // max luminance
+ public double minlum = BIG_VALUE;
+ public double[] maxdif = { BIG_VALUE_NEG, BIG_VALUE_NEG, BIG_VALUE_NEG, BIG_VALUE }; // maxima
+ public final int channels; // diferencia
+
+ @Override
+ public String toString() {
+ return channels == 3 ? String.format(
+ "prom=%.1f (%.1f %.1f %.1f) max=%.1f (%.1f %.1f %.1f) min=%.1f (%.1f %.1f %.1f)", promlum, prom[0],
+ prom[1], prom[2], maxlum, maxv[0], maxv[1], maxv[2], minlum, minv[0], minv[1], minv[2])
+ + String.format(" maxdif=(%.1f %.1f %.1f)", maxdif[0], maxdif[1], maxdif[2]) : String.format(
+ "prom=%.1f (%.1f %.1f %.1f %.1f) max=%.1f (%.1f %.1f %.1f %.1f) min=%.1f (%.1f %.1f %.1f %.1f)",
+ promlum, prom[0], prom[1], prom[2], prom[3], maxlum, maxv[0], maxv[1], maxv[2], maxv[3], minlum,
+ minv[0], minv[1], minv[2], minv[3])
+ + String.format(" maxdif=(%.1f %.1f %.1f %.1f)", maxdif[0], maxdif[1], maxdif[2], maxdif[3]);
+ }
+
+ public ImageLineStats(final ImageLine line) {
+ this.channels = line.channels;
+ if (line.channels < 3)
+ throw new PngjException("ImageLineStats only works for RGB - RGBA");
+ int ch = 0;
+ double lum, x, d;
+ for (int i = 0; i < line.imgInfo.cols; i++) {
+ lum = 0;
+ for (ch = channels - 1; ch >= 0; ch--) {
+ x = int2double(line, line.scanline[i * channels]);
+ if (ch < 3)
+ lum += x;
+ prom[ch] += x;
+ if (x > maxv[ch])
+ maxv[ch] = x;
+ if (x < minv[ch])
+ minv[ch] = x;
+ if (i >= channels) {
+ d = Math.abs(x - int2double(line, line.scanline[i - channels]));
+ if (d > maxdif[ch])
+ maxdif[ch] = d;
+ }
+ }
+ promlum += lum;
+ if (lum > maxlum)
+ maxlum = lum;
+ if (lum < minlum)
+ minlum = lum;
+ }
+ for (ch = 0; ch < channels; ch++) {
+ prom[ch] /= line.imgInfo.cols;
+ }
+ promlum /= (line.imgInfo.cols * 3.0);
+ maxlum /= 3.0;
+ minlum /= 3.0;
+ }
+ }
+
+ /**
+ * integer packed R G B only for bitdepth=8! (does not check!)
+ *
+ **/
+ public static int getPixelRGB8(final ImageLine line, final int column) {
+ final int offset = column * line.channels;
+ return (line.scanline[offset] << 16) + (line.scanline[offset + 1] << 8) + (line.scanline[offset + 2]);
+ }
+
+ public static int getPixelARGB8(final ImageLine line, final int column) {
+ final int offset = column * line.channels;
+ return (line.scanline[offset + 3] << 24) + (line.scanline[offset] << 16) + (line.scanline[offset + 1] << 8)
+ + (line.scanline[offset + 2]);
+ }
+
+ public static void setPixelsRGB8(final ImageLine line, final int[] rgb) {
+ for (int i = 0, j = 0; i < line.imgInfo.cols; i++) {
+ line.scanline[j++] = ((rgb[i] >> 16) & 0xFF);
+ line.scanline[j++] = ((rgb[i] >> 8) & 0xFF);
+ line.scanline[j++] = ((rgb[i] & 0xFF));
+ }
+ }
+
+ public static void setPixelRGB8(final ImageLine line, int col, final int r, final int g, final int b) {
+ col *= line.channels;
+ line.scanline[col++] = r;
+ line.scanline[col++] = g;
+ line.scanline[col] = b;
+ }
+
+ public static void setPixelRGB8(final ImageLine line, final int col, final int rgb) {
+ setPixelRGB8(line, col, (rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
+ }
+
+ public static void setPixelsRGBA8(final ImageLine line, final int[] rgb) {
+ for (int i = 0, j = 0; i < line.imgInfo.cols; i++) {
+ line.scanline[j++] = ((rgb[i] >> 16) & 0xFF);
+ line.scanline[j++] = ((rgb[i] >> 8) & 0xFF);
+ line.scanline[j++] = ((rgb[i] & 0xFF));
+ line.scanline[j++] = ((rgb[i] >> 24) & 0xFF);
+ }
+ }
+
+ public static void setPixelRGBA8(final ImageLine line, int col, final int r, final int g, final int b, final int a) {
+ col *= line.channels;
+ line.scanline[col++] = r;
+ line.scanline[col++] = g;
+ line.scanline[col++] = b;
+ line.scanline[col] = a;
+ }
+
+ public static void setPixelRGBA8(final ImageLine line, final int col, final int rgb) {
+ setPixelRGBA8(line, col, (rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, (rgb >> 24) & 0xFF);
+ }
+
+ public static void setValD(final ImageLine line, final int i, final double d) {
+ line.scanline[i] = double2int(line, d);
+ }
+
+ public static int interpol(final int a, final int b, final int c, final int d, final double dx, final double dy) {
+ // a b -> x (0-1)
+ // c d
+ //
+ final double e = a * (1.0 - dx) + b * dx;
+ final double f = c * (1.0 - dx) + d * dx;
+ return (int) (e * (1 - dy) + f * dy + 0.5);
+ }
+
+ public static double int2double(final ImageLine line, final int p) {
+ return line.bitDepth == 16 ? p / 65535.0 : p / 255.0;
+ // TODO: replace my multiplication? check for other bitdepths
+ }
+
+ public static double int2doubleClamped(final ImageLine line, final int p) {
+ // TODO: replace my multiplication?
+ final double d = line.bitDepth == 16 ? p / 65535.0 : p / 255.0;
+ return d <= 0.0 ? 0 : (d >= 1.0 ? 1.0 : d);
+ }
+
+ public static int double2int(final ImageLine line, double d) {
+ d = d <= 0.0 ? 0 : (d >= 1.0 ? 1.0 : d);
+ return line.bitDepth == 16 ? (int) (d * 65535.0 + 0.5) : (int) (d * 255.0 + 0.5); //
+ }
+
+ public static int double2intClamped(final ImageLine line, double d) {
+ d = d <= 0.0 ? 0 : (d >= 1.0 ? 1.0 : d);
+ return line.bitDepth == 16 ? (int) (d * 65535.0 + 0.5) : (int) (d * 255.0 + 0.5); //
+ }
+
+ public static int clampTo_0_255(final int i) {
+ return i > 255 ? 255 : (i < 0 ? 0 : i);
+ }
+
+ public static int clampTo_0_65535(final int i) {
+ return i > 65535 ? 65535 : (i < 0 ? 0 : i);
+ }
+
+ public static int clampTo_128_127(final int x) {
+ return x > 127 ? 127 : (x < -128 ? -128 : x);
+ }
+
+ /**
+ * Unpacks scanline (for bitdepth 1-2-4) into a array <code>int[]</code>
+ * <p>
+ * You can (OPTIONALLY) pass an preallocated array, that will be filled and
+ * returned. If null, it will be allocated
+ * <p>
+ * If
+ * <code>scale==true<code>, it scales the value (just a bit shift) towards 0-255.
+ * <p>
+ * You probably should use {@link ImageLine#unpackToNewImageLine()}
+ *
+ */
+ public static int[] unpack(final ImageInfo imgInfo, final int[] src, int[] dst, final boolean scale) {
+ final int len1 = imgInfo.samplesPerRow;
+ final int len0 = imgInfo.samplesPerRowPacked;
+ if (dst == null || dst.length < len1)
+ dst = new int[len1];
+ if (imgInfo.packed)
+ ImageLine.unpackInplaceInt(imgInfo, src, dst, scale);
+ else
+ System.arraycopy(src, 0, dst, 0, len0);
+ return dst;
+ }
+
+ public static byte[] unpack(final ImageInfo imgInfo, final byte[] src, byte[] dst, final boolean scale) {
+ final int len1 = imgInfo.samplesPerRow;
+ final int len0 = imgInfo.samplesPerRowPacked;
+ if (dst == null || dst.length < len1)
+ dst = new byte[len1];
+ if (imgInfo.packed)
+ ImageLine.unpackInplaceByte(imgInfo, src, dst, scale);
+ else
+ System.arraycopy(src, 0, dst, 0, len0);
+ return dst;
+ }
+
+ /**
+ * Packs scanline (for bitdepth 1-2-4) from array into the scanline
+ * <p>
+ * If <code>scale==true<code>, it scales the value (just a bit shift).
+ *
+ * You probably should use {@link ImageLine#packToNewImageLine()}
+ */
+ public static int[] pack(final ImageInfo imgInfo, final int[] src, int[] dst, final boolean scale) {
+ final int len0 = imgInfo.samplesPerRowPacked;
+ if (dst == null || dst.length < len0)
+ dst = new int[len0];
+ if (imgInfo.packed)
+ ImageLine.packInplaceInt(imgInfo, src, dst, scale);
+ else
+ System.arraycopy(src, 0, dst, 0, len0);
+ return dst;
+ }
+
+ public static byte[] pack(final ImageInfo imgInfo, final byte[] src, byte[] dst, final boolean scale) {
+ final int len0 = imgInfo.samplesPerRowPacked;
+ if (dst == null || dst.length < len0)
+ dst = new byte[len0];
+ if (imgInfo.packed)
+ ImageLine.packInplaceByte(imgInfo, src, dst, scale);
+ else
+ System.arraycopy(src, 0, dst, 0, len0);
+ return dst;
+ }
+
+ static int getMaskForPackedFormats(final int bitDepth) { // Utility function for pack/unpack
+ if (bitDepth == 4)
+ return 0xf0;
+ else if (bitDepth == 2)
+ return 0xc0;
+ else
+ return 0x80; // bitDepth == 1
+ }
+
+ static int getMaskForPackedFormatsLs(final int bitDepth) { // Utility function for pack/unpack
+ if (bitDepth == 4)
+ return 0x0f;
+ else if (bitDepth == 2)
+ return 0x03;
+ else
+ return 0x01; // bitDepth == 1
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/ImageLines.java b/src/jogl/classes/jogamp/opengl/util/pngj/ImageLines.java
new file mode 100644
index 000000000..8f6216ab2
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/ImageLines.java
@@ -0,0 +1,107 @@
+package jogamp.opengl.util.pngj;
+
+import jogamp.opengl.util.pngj.ImageLine.SampleType;
+
+/**
+ * Wraps in a matrix a set of image rows, not necessarily contiguous - but
+ * equispaced.
+ *
+ * The fields mirrors those of {@link ImageLine}, and you can access each row as
+ * a ImageLine backed by the matrix row, see
+ * {@link #getImageLineAtMatrixRow(int)}
+ */
+public class ImageLines {
+
+ public final ImageInfo imgInfo;
+ public final int channels;
+ public final int bitDepth;
+ public final SampleType sampleType;
+ public final boolean samplesUnpacked;
+ public final int elementsPerRow;
+ public final int rowOffset;
+ public final int nRows;
+ public final int rowStep;
+ public final int[][] scanlines;
+ public final byte[][] scanlinesb;
+
+ /**
+ * Allocates a matrix to store {@code nRows} image rows. See
+ * {@link ImageLine} and {@link PngReader#readRowsInt()}
+ * {@link PngReader#readRowsByte()}
+ *
+ * @param imgInfo
+ * @param stype
+ * @param unpackedMode
+ * @param rowOffset
+ * @param nRows
+ * @param rowStep
+ */
+ public ImageLines(final ImageInfo imgInfo, final SampleType stype, final boolean unpackedMode, final int rowOffset, final int nRows, final int rowStep) {
+ this.imgInfo = imgInfo;
+ channels = imgInfo.channels;
+ bitDepth = imgInfo.bitDepth;
+ this.sampleType = stype;
+ this.samplesUnpacked = unpackedMode || !imgInfo.packed;
+ elementsPerRow = unpackedMode ? imgInfo.samplesPerRow : imgInfo.samplesPerRowPacked;
+ this.rowOffset = rowOffset;
+ this.nRows = nRows;
+ this.rowStep = rowStep;
+ if (stype == SampleType.INT) {
+ scanlines = new int[nRows][elementsPerRow];
+ scanlinesb = null;
+ } else if (stype == SampleType.BYTE) {
+ scanlinesb = new byte[nRows][elementsPerRow];
+ scanlines = null;
+ } else
+ throw new PngjExceptionInternal("bad ImageLine initialization");
+ }
+
+ /**
+ * Warning: this always returns a valid matrix row (clamping on 0 : nrows-1,
+ * and rounding down) Eg: rowOffset=4,rowStep=2 imageRowToMatrixRow(17)
+ * returns 6 , imageRowToMatrixRow(1) returns 0
+ */
+ public int imageRowToMatrixRow(final int imrow) {
+ final int r = (imrow - rowOffset) / rowStep;
+ return r < 0 ? 0 : (r < nRows ? r : nRows - 1);
+ }
+
+ /**
+ * Same as imageRowToMatrixRow, but returns negative if invalid
+ */
+ public int imageRowToMatrixRowStrict(int imrow) {
+ imrow -= rowOffset;
+ final int mrow = imrow >= 0 && imrow % rowStep == 0 ? imrow / rowStep : -1;
+ return mrow < nRows ? mrow : -1;
+ }
+
+ /**
+ * Converts from matrix row number (0 : nRows-1) to image row number
+ *
+ * @param mrow
+ * Matrix row number
+ * @return Image row number. Invalid only if mrow is invalid
+ */
+ public int matrixRowToImageRow(final int mrow) {
+ return mrow * rowStep + rowOffset;
+ }
+
+ /**
+ * Returns a ImageLine is backed by the matrix, no allocation done
+ *
+ * @param mrow
+ * Matrix row, from 0 to nRows This is not necessarily the image
+ * row, see {@link #imageRowToMatrixRow(int)} and
+ * {@link #matrixRowToImageRow(int)}
+ * @return A new ImageLine, backed by the matrix, with the correct ('real')
+ * rownumber
+ */
+ public ImageLine getImageLineAtMatrixRow(final int mrow) {
+ if (mrow < 0 || mrow > nRows)
+ throw new PngjException("Bad row " + mrow + ". Should be positive and less than " + nRows);
+ final ImageLine imline = sampleType == SampleType.INT ? new ImageLine(imgInfo, sampleType, samplesUnpacked,
+ scanlines[mrow], null) : new ImageLine(imgInfo, sampleType, samplesUnpacked, null, scanlinesb[mrow]);
+ imline.setRown(matrixRowToImageRow(mrow));
+ return imline;
+ }
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngDeinterlacer.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngDeinterlacer.java
new file mode 100644
index 000000000..436821cf7
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngDeinterlacer.java
@@ -0,0 +1,277 @@
+package jogamp.opengl.util.pngj;
+
+import java.util.Random;
+
+// you really dont' want to peek inside this
+class PngDeinterlacer {
+ private final ImageInfo imi;
+ private int pass; // 1-7
+ private int rows, cols, dY, dX, oY, oX, oXsamples, dXsamples; // at current pass
+ // current row in the virtual subsampled image; this incrementes from 0 to cols/dy 7 times
+ private int currRowSubimg = -1;
+ // in the real image, this will cycle from 0 to im.rows in different steps, 7 times
+ private int currRowReal = -1;
+
+ private final int packedValsPerPixel;
+ private final int packedMask;
+ private final int packedShift;
+
+ private int[][] imageInt; // FULL image -only used for PngWriter as temporary storage
+ private short[][] imageShort;
+ private byte[][] imageByte;
+
+ PngDeinterlacer(final ImageInfo iminfo) {
+ this.imi = iminfo;
+ pass = 0;
+ if (imi.packed) {
+ packedValsPerPixel = 8 / imi.bitDepth;
+ packedShift = imi.bitDepth;
+ if (imi.bitDepth == 1)
+ packedMask = 0x80;
+ else if (imi.bitDepth == 2)
+ packedMask = 0xc0;
+ else
+ packedMask = 0xf0;
+ } else {
+ packedMask = packedShift = packedValsPerPixel = 1;// dont care
+ }
+ setPass(1);
+ setRow(0);
+ }
+
+ /** this refers to the row currRowSubimg */
+ void setRow(final int n) {
+ currRowSubimg = n;
+ currRowReal = n * dY + oY;
+ if (currRowReal < 0 || currRowReal >= imi.rows)
+ throw new PngjExceptionInternal("bad row - this should not happen");
+ }
+
+ void setPass(final int p) {
+ if (this.pass == p)
+ return;
+ pass = p;
+ switch (pass) {
+ case 1:
+ dY = dX = 8;
+ oX = oY = 0;
+ break;
+ case 2:
+ dY = dX = 8;
+ oX = 4;
+ oY = 0;
+ break;
+ case 3:
+ dX = 4;
+ dY = 8;
+ oX = 0;
+ oY = 4;
+ break;
+ case 4:
+ dX = dY = 4;
+ oX = 2;
+ oY = 0;
+ break;
+ case 5:
+ dX = 2;
+ dY = 4;
+ oX = 0;
+ oY = 2;
+ break;
+ case 6:
+ dX = dY = 2;
+ oX = 1;
+ oY = 0;
+ break;
+ case 7:
+ dX = 1;
+ dY = 2;
+ oX = 0;
+ oY = 1;
+ break;
+ default:
+ throw new PngjExceptionInternal("bad interlace pass" + pass);
+ }
+ rows = (imi.rows - oY) / dY + 1;
+ if ((rows - 1) * dY + oY >= imi.rows)
+ rows--; // can be 0
+ cols = (imi.cols - oX) / dX + 1;
+ if ((cols - 1) * dX + oX >= imi.cols)
+ cols--; // can be 0
+ if (cols == 0)
+ rows = 0; // really...
+ dXsamples = dX * imi.channels;
+ oXsamples = oX * imi.channels;
+ }
+
+ // notice that this is a "partial" deinterlace, it will be called several times for the same row!
+ void deinterlaceInt(final int[] src, final int[] dst, final boolean readInPackedFormat) {
+ if (!(imi.packed && readInPackedFormat))
+ for (int i = 0, j = oXsamples; i < cols * imi.channels; i += imi.channels, j += dXsamples)
+ for (int k = 0; k < imi.channels; k++)
+ dst[j + k] = src[i + k];
+ else
+ deinterlaceIntPacked(src, dst);
+ }
+
+ // interlaced+packed = monster; this is very clumsy!
+ private void deinterlaceIntPacked(final int[] src, final int[] dst) {
+ int spos, smod, smask; // source byte position, bits to shift to left (01,2,3,4
+ int tpos, tmod, p, d;
+ spos = 0;
+ smask = packedMask;
+ smod = -1;
+ // can this really work?
+ for (int i = 0, j = oX; i < cols; i++, j += dX) {
+ spos = i / packedValsPerPixel;
+ smod += 1;
+ if (smod >= packedValsPerPixel)
+ smod = 0;
+ smask >>= packedShift; // the source mask cycles
+ if (smod == 0)
+ smask = packedMask;
+ tpos = j / packedValsPerPixel;
+ tmod = j % packedValsPerPixel;
+ p = src[spos] & smask;
+ d = tmod - smod;
+ if (d > 0)
+ p >>= (d * packedShift);
+ else if (d < 0)
+ p <<= ((-d) * packedShift);
+ dst[tpos] |= p;
+ }
+ }
+
+ // yes, duplication of code is evil, normally
+ void deinterlaceByte(final byte[] src, final byte[] dst, final boolean readInPackedFormat) {
+ if (!(imi.packed && readInPackedFormat))
+ for (int i = 0, j = oXsamples; i < cols * imi.channels; i += imi.channels, j += dXsamples)
+ for (int k = 0; k < imi.channels; k++)
+ dst[j + k] = src[i + k];
+ else
+ deinterlacePackedByte(src, dst);
+ }
+
+ private void deinterlacePackedByte(final byte[] src, final byte[] dst) {
+ int spos, smod, smask; // source byte position, bits to shift to left (01,2,3,4
+ int tpos, tmod, p, d;
+ // what the heck are you reading here? I told you would not enjoy this. Try Dostoyevsky or Simone Weil instead
+ spos = 0;
+ smask = packedMask;
+ smod = -1;
+ // Arrays.fill(dst, 0);
+ for (int i = 0, j = oX; i < cols; i++, j += dX) {
+ spos = i / packedValsPerPixel;
+ smod += 1;
+ if (smod >= packedValsPerPixel)
+ smod = 0;
+ smask >>= packedShift; // the source mask cycles
+ if (smod == 0)
+ smask = packedMask;
+ tpos = j / packedValsPerPixel;
+ tmod = j % packedValsPerPixel;
+ p = src[spos] & smask;
+ d = tmod - smod;
+ if (d > 0)
+ p >>= (d * packedShift);
+ else if (d < 0)
+ p <<= ((-d) * packedShift);
+ dst[tpos] |= p;
+ }
+ }
+
+ /**
+ * Is current row the last row for the lass pass??
+ */
+ boolean isAtLastRow() {
+ return pass == 7 && currRowSubimg == rows - 1;
+ }
+
+ /**
+ * current row number inside the "sub image"
+ */
+ int getCurrRowSubimg() {
+ return currRowSubimg;
+ }
+
+ /**
+ * current row number inside the "real image"
+ */
+ int getCurrRowReal() {
+ return currRowReal;
+ }
+
+ /**
+ * current pass number (1-7)
+ */
+ int getPass() {
+ return pass;
+ }
+
+ /**
+ * How many rows has the current pass?
+ **/
+ int getRows() {
+ return rows;
+ }
+
+ /**
+ * How many columns (pixels) are there in the current row
+ */
+ int getCols() {
+ return cols;
+ }
+
+ public int getPixelsToRead() {
+ return getCols();
+ }
+
+ int[][] getImageInt() {
+ return imageInt;
+ }
+
+ void setImageInt(final int[][] imageInt) {
+ this.imageInt = imageInt;
+ }
+
+ short[][] getImageShort() {
+ return imageShort;
+ }
+
+ void setImageShort(final short[][] imageShort) {
+ this.imageShort = imageShort;
+ }
+
+ byte[][] getImageByte() {
+ return imageByte;
+ }
+
+ void setImageByte(final byte[][] imageByte) {
+ this.imageByte = imageByte;
+ }
+
+ static void test() {
+ final Random rand = new Random();
+ final PngDeinterlacer ih = new PngDeinterlacer(new ImageInfo(rand.nextInt(35) + 1, rand.nextInt(52) + 1, 8, true));
+ int np = ih.imi.cols * ih.imi.rows;
+ System.out.println(ih.imi);
+ for (int p = 1; p <= 7; p++) {
+ ih.setPass(p);
+ for (int row = 0; row < ih.getRows(); row++) {
+ ih.setRow(row);
+ final int b = ih.getCols();
+ np -= b;
+ System.out.printf("Read %d pixels. Pass:%d Realline:%d cols=%d dX=%d oX=%d last:%b\n", b, ih.pass,
+ ih.currRowReal, ih.cols, ih.dX, ih.oX, ih.isAtLastRow());
+
+ }
+ }
+ if (np != 0)
+ throw new PngjExceptionInternal("wtf??" + ih.imi);
+ }
+
+ public static void main(final String[] args) {
+ test();
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java
deleted file mode 100644
index 1016b1b64..000000000
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java
+++ /dev/null
@@ -1,213 +0,0 @@
-package jogamp.opengl.util.pngj;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.zip.CRC32;
-
-/**
- * Some utility static methods.
- * <p>
- * See also <code>FileHelper</code> (if not sandboxed).
- * <p>
- * Client code should rarely need these methods.
- */
-public class PngHelper {
- /**
- * Default charset, used internally by PNG for several things
- */
- public static Charset charsetLatin1 = Charset.forName("ISO-8859-1");
- public static Charset charsetUTF8 = Charset.forName("UTF-8"); // only for some chunks
-
- static boolean DEBUG = false;
-
- public static int readByte(InputStream is) {
- try {
- return is.read();
- } catch (IOException e) {
- throw new PngjOutputException(e);
- }
- }
-
- /**
- * -1 if eof
- *
- * PNG uses "network byte order"
- */
- public static int readInt2(InputStream is) {
- try {
- int b1 = is.read();
- int b2 = is.read();
- if (b1 == -1 || b2 == -1)
- return -1;
- return (b1 << 8) + b2;
- } catch (IOException e) {
- throw new PngjInputException("error reading readInt2", e);
- }
- }
-
- /**
- * -1 if eof
- */
- public static int readInt4(InputStream is) {
- try {
- int b1 = is.read();
- int b2 = is.read();
- int b3 = is.read();
- int b4 = is.read();
- if (b1 == -1 || b2 == -1 || b3 == -1 || b4 == -1)
- return -1;
- return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4;
- } catch (IOException e) {
- throw new PngjInputException("error reading readInt4", e);
- }
- }
-
- public static int readInt1fromByte(byte[] b, int offset) {
- return (b[offset] & 0xff);
- }
-
- public static int readInt2fromBytes(byte[] b, int offset) {
- return ((b[offset] & 0xff) << 16) | ((b[offset + 1] & 0xff));
- }
-
- public static int readInt4fromBytes(byte[] b, int offset) {
- return ((b[offset] & 0xff) << 24) | ((b[offset + 1] & 0xff) << 16) | ((b[offset + 2] & 0xff) << 8)
- | (b[offset + 3] & 0xff);
- }
-
- public static void writeByte(OutputStream os, byte b) {
- try {
- os.write(b);
- } catch (IOException e) {
- throw new PngjOutputException(e);
- }
- }
-
- public static void writeInt2(OutputStream os, int n) {
- byte[] temp = { (byte) ((n >> 8) & 0xff), (byte) (n & 0xff) };
- writeBytes(os, temp);
- }
-
- public static void writeInt4(OutputStream os, int n) {
- byte[] temp = new byte[4];
- writeInt4tobytes(n, temp, 0);
- writeBytes(os, temp);
- }
-
- public static void writeInt2tobytes(int n, byte[] b, int offset) {
- b[offset] = (byte) ((n >> 8) & 0xff);
- b[offset + 1] = (byte) (n & 0xff);
- }
-
- public static void writeInt4tobytes(int n, byte[] b, int offset) {
- b[offset] = (byte) ((n >> 24) & 0xff);
- b[offset + 1] = (byte) ((n >> 16) & 0xff);
- b[offset + 2] = (byte) ((n >> 8) & 0xff);
- b[offset + 3] = (byte) (n & 0xff);
- }
-
- /**
- * guaranteed to read exactly len bytes. throws error if it cant
- */
- public static void readBytes(InputStream is, byte[] b, int offset, int len) {
- if (len == 0)
- return;
- try {
- int read = 0;
- while (read < len) {
- int n = is.read(b, offset + read, len - read);
- if (n < 1)
- throw new RuntimeException("error reading bytes, " + n + " !=" + len);
- read += n;
- }
- } catch (IOException e) {
- throw new PngjInputException("error reading", e);
- }
- }
-
- public static void writeBytes(OutputStream os, byte[] b) {
- try {
- os.write(b);
- } catch (IOException e) {
- throw new PngjOutputException(e);
- }
- }
-
- public static void writeBytes(OutputStream os, byte[] b, int offset, int n) {
- try {
- os.write(b, offset, n);
- } catch (IOException e) {
- throw new PngjOutputException(e);
- }
- }
-
- public static void logdebug(String msg) {
- if (DEBUG)
- System.out.println(msg);
- }
-
- public static Set<String> asSet(String... values) {
- return new HashSet<String>(java.util.Arrays.asList(values));
- }
-
- public static Set<String> unionSets(Set<String> set1, Set<String> set2) {
- Set<String> s = new HashSet<String>();
- s.addAll(set1);
- s.addAll(set2);
- return s;
- }
-
- public static Set<String> unionSets(Set<String> set1, Set<String> set2, Set<String> set3) {
- Set<String> s = new HashSet<String>();
- s.addAll(set1);
- s.addAll(set2);
- s.addAll(set3);
- return s;
- }
-
- private static final ThreadLocal<CRC32> crcProvider = new ThreadLocal<CRC32>() {
- protected CRC32 initialValue() {
- return new CRC32();
- }
- };
-
- /** thread-singleton crc engine */
- public static CRC32 getCRC() {
- return crcProvider.get();
- }
-
- static final byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 }; // png magic
-
- public static double resMetersToDpi(long res) {
- return (double) res * 0.0254;
- }
-
- public static long resDpiToMeters(double dpi) {
- return (long) (dpi / 0.0254 + 0.5);
- }
-
- public static int doubleToInt100000(double d) {
- return (int) (d * 100000.0 + 0.5);
- }
-
- public static double intToDouble100000(int i) {
- return i / 100000.0;
- }
-
- public static int clampTo_0_255(int i) {
- return i > 255 ? 255 : (i < 0 ? 0 : i);
- }
-
- public static int clampTo_0_65535(int i) {
- return i > 65535 ? 65535 : (i < 0 ? 0 : i);
- }
-
- public static int clampTo_128_127(int x) {
- return x > 127 ? 127 : (x < -128 ? -128 : x);
- }
-
-}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngHelperInternal.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngHelperInternal.java
new file mode 100644
index 000000000..48e5e8517
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngHelperInternal.java
@@ -0,0 +1,270 @@
+package jogamp.opengl.util.pngj;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.zip.CRC32;
+
+/**
+ * Some utility static methods for internal use.
+ * <p>
+ * Client code should not normally use this class
+ * <p>
+ */
+public class PngHelperInternal {
+ /**
+ * Default charset, used internally by PNG for several things
+ */
+ public static final Charset charsetLatin1 = Charset.forName("ISO-8859-1");
+ /**
+ * UTF-8 is only used for some chunks
+ */
+ public static final Charset charsetUTF8 = Charset.forName("UTF-8");
+
+ static final boolean DEBUG = false;
+
+ /**
+ * PNG magic bytes
+ */
+ public static byte[] getPngIdSignature() {
+ return new byte[] { -119, 80, 78, 71, 13, 10, 26, 10 };
+ }
+
+ public static int doubleToInt100000(final double d) {
+ return (int) (d * 100000.0 + 0.5);
+ }
+
+ public static double intToDouble100000(final int i) {
+ return i / 100000.0;
+ }
+
+ public static int readByte(final InputStream is) {
+ try {
+ return is.read();
+ } catch (final IOException e) {
+ throw new PngjInputException("error reading byte", e);
+ }
+ }
+
+ /**
+ * -1 if eof
+ *
+ * PNG uses "network byte order"
+ */
+ public static int readInt2(final InputStream is) {
+ try {
+ final int b1 = is.read();
+ final int b2 = is.read();
+ if (b1 == -1 || b2 == -1)
+ return -1;
+ return (b1 << 8) + b2;
+ } catch (final IOException e) {
+ throw new PngjInputException("error reading readInt2", e);
+ }
+ }
+
+ /**
+ * -1 if eof
+ */
+ public static int readInt4(final InputStream is) {
+ try {
+ final int b1 = is.read();
+ final int b2 = is.read();
+ final int b3 = is.read();
+ final int b4 = is.read();
+ if (b1 == -1 || b2 == -1 || b3 == -1 || b4 == -1)
+ return -1;
+ return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4;
+ } catch (final IOException e) {
+ throw new PngjInputException("error reading readInt4", e);
+ }
+ }
+
+ public static int readInt1fromByte(final byte[] b, final int offset) {
+ return (b[offset] & 0xff);
+ }
+
+ public static int readInt2fromBytes(final byte[] b, final int offset) {
+ return ((b[offset] & 0xff) << 16) | ((b[offset + 1] & 0xff));
+ }
+
+ public static int readInt4fromBytes(final byte[] b, final int offset) {
+ return ((b[offset] & 0xff) << 24) | ((b[offset + 1] & 0xff) << 16) | ((b[offset + 2] & 0xff) << 8)
+ | (b[offset + 3] & 0xff);
+ }
+
+ public static void writeByte(final OutputStream os, final byte b) {
+ try {
+ os.write(b);
+ } catch (final IOException e) {
+ throw new PngjOutputException(e);
+ }
+ }
+
+ public static void writeInt2(final OutputStream os, final int n) {
+ final byte[] temp = { (byte) ((n >> 8) & 0xff), (byte) (n & 0xff) };
+ writeBytes(os, temp);
+ }
+
+ public static void writeInt4(final OutputStream os, final int n) {
+ final byte[] temp = new byte[4];
+ writeInt4tobytes(n, temp, 0);
+ writeBytes(os, temp);
+ }
+
+ public static void writeInt2tobytes(final int n, final byte[] b, final int offset) {
+ b[offset] = (byte) ((n >> 8) & 0xff);
+ b[offset + 1] = (byte) (n & 0xff);
+ }
+
+ public static void writeInt4tobytes(final int n, final byte[] b, final int offset) {
+ b[offset] = (byte) ((n >> 24) & 0xff);
+ b[offset + 1] = (byte) ((n >> 16) & 0xff);
+ b[offset + 2] = (byte) ((n >> 8) & 0xff);
+ b[offset + 3] = (byte) (n & 0xff);
+ }
+
+ /**
+ * guaranteed to read exactly len bytes. throws error if it can't
+ */
+ public static void readBytes(final InputStream is, final byte[] b, final int offset, final int len) {
+ if (len == 0)
+ return;
+ try {
+ int read = 0;
+ while (read < len) {
+ final int n = is.read(b, offset + read, len - read);
+ if (n < 1)
+ throw new PngjInputException("error reading bytes, " + n + " !=" + len);
+ read += n;
+ }
+ } catch (final IOException e) {
+ throw new PngjInputException("error reading", e);
+ }
+ }
+
+ public static void skipBytes(final InputStream is, long len) {
+ try {
+ while (len > 0) {
+ final long n1 = is.skip(len);
+ if (n1 > 0) {
+ len -= n1;
+ } else if (n1 == 0) { // should we retry? lets read one byte
+ if (is.read() == -1) // EOF
+ break;
+ else
+ len--;
+ } else
+ // negative? this should never happen but...
+ throw new IOException("skip() returned a negative value ???");
+ }
+ } catch (final IOException e) {
+ throw new PngjInputException(e);
+ }
+ }
+
+ public static void writeBytes(final OutputStream os, final byte[] b) {
+ try {
+ os.write(b);
+ } catch (final IOException e) {
+ throw new PngjOutputException(e);
+ }
+ }
+
+ public static void writeBytes(final OutputStream os, final byte[] b, final int offset, final int n) {
+ try {
+ os.write(b, offset, n);
+ } catch (final IOException e) {
+ throw new PngjOutputException(e);
+ }
+ }
+
+ public static void logdebug(final String msg) {
+ if (DEBUG)
+ System.out.println(msg);
+ }
+
+ private static final ThreadLocal<CRC32> crcProvider = new ThreadLocal<CRC32>() {
+ @Override
+ protected CRC32 initialValue() {
+ return new CRC32();
+ }
+ };
+
+ /** thread-singleton crc engine */
+ public static CRC32 getCRC() {
+ return crcProvider.get();
+ }
+
+ // / filters
+ public static int filterRowNone(final int r) {
+ return r & 0xFF;
+ }
+
+ public static int filterRowSub(final int r, final int left) {
+ return (r - left & 0xFF);
+ }
+
+ public static int filterRowUp(final int r, final int up) {
+ return (r - up & 0xFF);
+ }
+
+ public static int filterRowAverage(final int r, final int left, final int up) {
+ return (r - (left + up) / 2) & 0xFF;
+ }
+
+ public static int filterRowPaeth(final int r, final int left, final int up, final int upleft) { // a = left, b = above, c = upper left
+ return (r - filterPaethPredictor(left, up, upleft)) & 0xFF;
+ }
+
+ public static int unfilterRowNone(final int r) {
+ return r & 0xFF;
+ }
+
+ public static int unfilterRowSub(final int r, final int left) {
+ return (r + left & 0xFF);
+ }
+
+ public static int unfilterRowUp(final int r, final int up) {
+ return (r + up & 0xFF);
+ }
+
+ public static int unfilterRowAverage(final int r, final int left, final int up) {
+ return (r + (left + up) / 2) & 0xFF;
+ }
+
+ public static int unfilterRowPaeth(final int r, final int left, final int up, final int upleft) { // a = left, b = above, c = upper left
+ return (r + filterPaethPredictor(left, up, upleft)) & 0xFF;
+ }
+
+ final static int filterPaethPredictor(final int a, final int b, final int c) { // a = left, b = above, c = upper
+ // left
+ // from http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html
+
+ final int p = a + b - c;// ; initial estimate
+ final int pa = p >= a ? p - a : a - p;
+ final int pb = p >= b ? p - b : b - p;
+ final int pc = p >= c ? p - c : c - p;
+ // ; return nearest of a,b,c,
+ // ; breaking ties in order a,b,c.
+ if (pa <= pb && pa <= pc)
+ return a;
+ else if (pb <= pc)
+ return b;
+ else
+ return c;
+ }
+
+ /*
+ * we put this methods here so as to not pollute the public interface of PngReader
+ */
+ public final static void initCrcForTests(final PngReader pngr) {
+ pngr.initCrctest();
+ }
+
+ public final static long getCrctestVal(final PngReader pngr) {
+ return pngr.getCrctestVal();
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkInputStream.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkInputStream.java
index 66c4b49f0..cde4b517e 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkInputStream.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkInputStream.java
@@ -11,23 +11,24 @@ import jogamp.opengl.util.pngj.chunks.ChunkHelper;
/**
- * Reads IDAT chunks
+ * Reads a sequence of contiguous IDAT chunks
*/
class PngIDatChunkInputStream extends InputStream {
private final InputStream inputStream;
private final CRC32 crcEngine;
+ private boolean checkCrc = true;
private int lenLastChunk;
- private byte[] idLastChunk = new byte[4];
+ private final byte[] idLastChunk = new byte[4];
private int toReadThisChunk = 0;
private boolean ended = false;
- private long offset; // offset inside inputstream
+ private long offset; // offset inside whole inputstream (counting bytes before IDAT)
// just informational
static class IdatChunkInfo {
public final int len;
- public final int offset;
+ public final long offset;
- private IdatChunkInfo(int len, int offset) {
+ private IdatChunkInfo(final int len, final long offset) {
this.len = len;
this.offset = offset;
}
@@ -36,18 +37,20 @@ class PngIDatChunkInputStream extends InputStream {
List<IdatChunkInfo> foundChunksInfo = new ArrayList<IdatChunkInfo>();
/**
- * Constructor must be called just after reading length and id of first IDAT chunk
+ * Constructor must be called just after reading length and id of first IDAT
+ * chunk
**/
- PngIDatChunkInputStream(InputStream iStream, int lenFirstChunk, int offset) {
- this.offset = (long) offset;
+ PngIDatChunkInputStream(final InputStream iStream, final int lenFirstChunk, final long offset) {
+ this.offset = offset;
inputStream = iStream;
- crcEngine = new CRC32();
this.lenLastChunk = lenFirstChunk;
toReadThisChunk = lenFirstChunk;
// we know it's a IDAT
System.arraycopy(ChunkHelper.b_IDAT, 0, idLastChunk, 0, 4);
+ crcEngine = new CRC32();
crcEngine.update(idLastChunk, 0, 4);
foundChunksInfo.add(new IdatChunkInfo(lenLastChunk, offset - 8));
+
// PngHelper.logdebug("IDAT Initial fragment: len=" + lenLastChunk);
if (this.lenLastChunk == 0)
endChunkGoForNext(); // rare, but...
@@ -58,31 +61,33 @@ class PngIDatChunkInputStream extends InputStream {
*/
@Override
public void close() throws IOException {
- super.close(); // nothing
+ super.close(); // thsi does nothing
}
private void endChunkGoForNext() {
- // Called after readging the last byte of chunk
+ // Called after readging the last byte of one IDAT chunk
// Checks CRC, and read ID from next CHUNK
// Those values are left in idLastChunk / lenLastChunk
// Skips empty IDATS
do {
- int crc = PngHelper.readInt4(inputStream); //
+ final int crc = PngHelperInternal.readInt4(inputStream); //
offset += 4;
- int crccalc = (int) crcEngine.getValue();
- if (lenLastChunk > 0 && crc != crccalc)
- throw new PngjBadCrcException("error reading idat; offset: " + offset);
- crcEngine.reset();
- lenLastChunk = PngHelper.readInt4(inputStream);
- if (lenLastChunk < 0)
- throw new PngjInputException("invalid len for chunk: " + lenLastChunk);
+ if (checkCrc) {
+ final int crccalc = (int) crcEngine.getValue();
+ if (lenLastChunk > 0 && crc != crccalc)
+ throw new PngjBadCrcException("error reading idat; offset: " + offset);
+ crcEngine.reset();
+ }
+ lenLastChunk = PngHelperInternal.readInt4(inputStream);
toReadThisChunk = lenLastChunk;
- PngHelper.readBytes(inputStream, idLastChunk, 0, 4);
+ PngHelperInternal.readBytes(inputStream, idLastChunk, 0, 4);
offset += 8;
+ // found a NON IDAT chunk? this stream is ended
ended = !Arrays.equals(idLastChunk, ChunkHelper.b_IDAT);
if (!ended) {
- foundChunksInfo.add(new IdatChunkInfo(lenLastChunk, (int) (offset - 8)));
- crcEngine.update(idLastChunk, 0, 4);
+ foundChunksInfo.add(new IdatChunkInfo(lenLastChunk, offset - 8));
+ if (checkCrc)
+ crcEngine.update(idLastChunk, 0, 4);
}
// PngHelper.logdebug("IDAT ended. next len= " + lenLastChunk + " idat?" +
// (!ended));
@@ -91,27 +96,33 @@ class PngIDatChunkInputStream extends InputStream {
}
/**
- * sometimes last row read does not fully consumes the chunk here we read the reamaing dummy bytes
+ * sometimes last row read does not fully consumes the chunk here we read
+ * the reamaing dummy bytes
*/
void forceChunkEnd() {
if (!ended) {
- byte[] dummy = new byte[toReadThisChunk];
- PngHelper.readBytes(inputStream, dummy, 0, toReadThisChunk);
- crcEngine.update(dummy, 0, toReadThisChunk);
+ final byte[] dummy = new byte[toReadThisChunk];
+ PngHelperInternal.readBytes(inputStream, dummy, 0, toReadThisChunk);
+ if (checkCrc)
+ crcEngine.update(dummy, 0, toReadThisChunk);
endChunkGoForNext();
}
}
/**
- * This can return less than len, but never 0 Returns -1 if "pseudo file" ended prematurely. That is our error.
+ * This can return less than len, but never 0 Returns -1 if "pseudo file"
+ * ended prematurely. That is our error.
*/
@Override
- public int read(byte[] b, int off, int len) throws IOException {
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ if (ended)
+ return -1; // can happen only when raw reading, see Pngreader.readAndSkipsAllRows()
if (toReadThisChunk == 0)
- throw new RuntimeException("this should not happen");
- int n = inputStream.read(b, off, len >= toReadThisChunk ? toReadThisChunk : len);
+ throw new PngjExceptionInternal("this should not happen");
+ final int n = inputStream.read(b, off, len >= toReadThisChunk ? toReadThisChunk : len);
if (n > 0) {
- crcEngine.update(b, off, n);
+ if (checkCrc)
+ crcEngine.update(b, off, n);
this.offset += n;
toReadThisChunk -= n;
}
@@ -122,7 +133,7 @@ class PngIDatChunkInputStream extends InputStream {
}
@Override
- public int read(byte[] b) throws IOException {
+ public int read(final byte[] b) throws IOException {
return this.read(b, 0, b.length);
}
@@ -130,8 +141,8 @@ class PngIDatChunkInputStream extends InputStream {
public int read() throws IOException {
// PngHelper.logdebug("read() should go here");
// inneficient - but this should be used rarely
- byte[] b1 = new byte[1];
- int r = this.read(b1, 0, 1);
+ final byte[] b1 = new byte[1];
+ final int r = this.read(b1, 0, 1);
return r < 0 ? -1 : (int) b1[0];
}
@@ -150,4 +161,11 @@ class PngIDatChunkInputStream extends InputStream {
boolean isEnded() {
return ended;
}
+
+ /**
+ * Disables CRC checking. This can make reading faster
+ */
+ void disableCrcCheck() {
+ checkCrc = false;
+ }
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java
index 8b9fa5dae..38b500cd3 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java
@@ -7,24 +7,24 @@ import jogamp.opengl.util.pngj.chunks.ChunkRaw;
/**
- * outputs the stream for IDAT chunk , fragmented at fixed size (16384 default).
+ * outputs the stream for IDAT chunk , fragmented at fixed size (32k default).
*/
class PngIDatChunkOutputStream extends ProgressiveOutputStream {
- private static final int SIZE_DEFAULT = 16384;
+ private static final int SIZE_DEFAULT = 32768; // 32k
private final OutputStream outputStream;
- PngIDatChunkOutputStream(OutputStream outputStream) {
- this(outputStream, SIZE_DEFAULT);
+ PngIDatChunkOutputStream(final OutputStream outputStream) {
+ this(outputStream, 0);
}
- PngIDatChunkOutputStream(OutputStream outputStream, int size) {
- super(size);
+ PngIDatChunkOutputStream(final OutputStream outputStream, final int size) {
+ super(size > 0 ? size : SIZE_DEFAULT);
this.outputStream = outputStream;
}
@Override
- public final void flushBuffer(byte[] b, int len) {
- ChunkRaw c = new ChunkRaw(len, ChunkHelper.b_IDAT, false);
+ protected final void flushBuffer(final byte[] b, final int len) {
+ final ChunkRaw c = new ChunkRaw(len, ChunkHelper.b_IDAT, false);
c.data = b;
c.writeChunk(outputStream);
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngReader.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngReader.java
index 7343893b6..f77d4f4e0 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngReader.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngReader.java
@@ -1,415 +1,1001 @@
-package jogamp.opengl.util.pngj;
-
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.zip.InflaterInputStream;
-
-import jogamp.opengl.util.pngj.PngIDatChunkInputStream.IdatChunkInfo;
-import jogamp.opengl.util.pngj.chunks.ChunkHelper;
-import jogamp.opengl.util.pngj.chunks.ChunkList;
-import jogamp.opengl.util.pngj.chunks.ChunkLoadBehaviour;
-import jogamp.opengl.util.pngj.chunks.ChunkRaw;
-import jogamp.opengl.util.pngj.chunks.PngChunk;
-import jogamp.opengl.util.pngj.chunks.PngChunkIHDR;
-import jogamp.opengl.util.pngj.chunks.PngMetadata;
-
-
-/**
- * Reads a PNG image, line by line
- */
-public class PngReader {
- /**
- * Basic image info - final and inmutable.
- */
- public final ImageInfo imgInfo;
- protected final String filename; // not necesarily a file, can be a description - merely informative
-
- private static int MAX_BYTES_CHUNKS_TO_LOAD = 640000;
- private ChunkLoadBehaviour chunkLoadBehaviour = ChunkLoadBehaviour.LOAD_CHUNK_ALWAYS;
-
- private final InputStream is;
- private InflaterInputStream idatIstream;
- private PngIDatChunkInputStream iIdatCstream;
-
- protected int currentChunkGroup = -1;
- protected int rowNum = -1; // current row number
- private int offset = 0;
- private int bytesChunksLoaded; // bytes loaded from anciallary chunks
-
- protected ImageLine imgLine;
-
- // line as bytes, counting from 1 (index 0 is reserved for filter type)
- protected byte[] rowb = null;
- protected byte[] rowbprev = null; // rowb previous
- protected byte[] rowbfilter = null; // current line 'filtered': exactly as in uncompressed stream
-
- /**
- * All chunks loaded. Criticals are included, except that all IDAT chunks appearance are replaced by a single
- * dummy-marker IDAT chunk. These might be copied to the PngWriter
- */
- private final ChunkList chunksList;
- private final PngMetadata metadata; // this a wrapper over chunks
-
- /**
- * Constructs a PngReader from an InputStream.
- * <p>
- * See also <code>FileHelper.createPngReader(File f)</code> if available.
- *
- * Reads only the signature and first chunk (IDHR)
- *
- * @param filenameOrDescription
- * : Optional, can be a filename or a description. Just for error/debug messages
- *
- */
- public PngReader(InputStream inputStream, String filenameOrDescription) {
- this.filename = filenameOrDescription == null ? "" : filenameOrDescription;
- this.is = inputStream;
- this.chunksList = new ChunkList(null);
- this.metadata = new PngMetadata(chunksList, true);
- // reads header (magic bytes)
- byte[] pngid = new byte[PngHelper.pngIdBytes.length];
- PngHelper.readBytes(is, pngid, 0, pngid.length);
- offset += pngid.length;
- if (!Arrays.equals(pngid, PngHelper.pngIdBytes))
- throw new PngjInputException("Bad PNG signature");
- // reads first chunk
- currentChunkGroup = ChunkList.CHUNK_GROUP_0_IDHR;
- int clen = PngHelper.readInt4(is);
- offset += 4;
- if (clen != 13)
- throw new RuntimeException("IDHR chunk len != 13 ?? " + clen);
- byte[] chunkid = new byte[4];
- PngHelper.readBytes(is, chunkid, 0, 4);
- if (!Arrays.equals(chunkid, ChunkHelper.b_IHDR))
- throw new PngjInputException("IHDR not found as first chunk??? [" + ChunkHelper.toString(chunkid) + "]");
- offset += 4;
- ChunkRaw chunk = new ChunkRaw(clen, chunkid, true);
- String chunkids = ChunkHelper.toString(chunkid);
- offset += chunk.readChunkData(is);
- PngChunkIHDR ihdr = (PngChunkIHDR) addChunkToList(chunk);
- boolean alpha = (ihdr.getColormodel() & 0x04) != 0;
- boolean palette = (ihdr.getColormodel() & 0x01) != 0;
- boolean grayscale = (ihdr.getColormodel() == 0 || ihdr.getColormodel() == 4);
- imgInfo = new ImageInfo(ihdr.getCols(), ihdr.getRows(), ihdr.getBitspc(), alpha, grayscale, palette);
- imgLine = new ImageLine(imgInfo);
- if (ihdr.getInterlaced() != 0)
- throw new PngjUnsupportedException("PNG interlaced not supported by this library");
- if (ihdr.getFilmeth() != 0 || ihdr.getCompmeth() != 0)
- throw new PngjInputException("compmethod o filtermethod unrecognized");
- if (ihdr.getColormodel() < 0 || ihdr.getColormodel() > 6 || ihdr.getColormodel() == 1
- || ihdr.getColormodel() == 5)
- throw new PngjInputException("Invalid colormodel " + ihdr.getColormodel());
- if (ihdr.getBitspc() != 1 && ihdr.getBitspc() != 2 && ihdr.getBitspc() != 4 && ihdr.getBitspc() != 8
- && ihdr.getBitspc() != 16)
- throw new PngjInputException("Invalid bit depth " + ihdr.getBitspc());
- // allocation: one extra byte for filter type one pixel
- rowbfilter = new byte[imgInfo.bytesPerRow + 1];
- rowb = new byte[imgInfo.bytesPerRow + 1];
- rowbprev = new byte[rowb.length];
- }
-
- private static class FoundChunkInfo {
- public final String id;
- public final int len;
- public final int offset;
- public final boolean loaded;
-
- private FoundChunkInfo(String id, int len, int offset, boolean loaded) {
- this.id = id;
- this.len = len;
- this.offset = offset;
- this.loaded = loaded;
- }
-
- public String toString() {
- return "chunk " + id + " len=" + len + " offset=" + offset + (this.loaded ? " " : " X ");
- }
- }
-
- private PngChunk addChunkToList(ChunkRaw chunk) {
- // this requires that the currentChunkGroup is ok
- PngChunk chunkType = PngChunk.factory(chunk, imgInfo);
- if (!chunkType.crit) {
- bytesChunksLoaded += chunk.len;
- }
- if (bytesChunksLoaded > MAX_BYTES_CHUNKS_TO_LOAD) {
- throw new PngjInputException("Chunk exceeded available space (" + MAX_BYTES_CHUNKS_TO_LOAD + ") chunk: "
- + chunk + " See PngReader.MAX_BYTES_CHUNKS_TO_LOAD\n");
- }
- chunksList.appendReadChunk(chunkType, currentChunkGroup);
- return chunkType;
- }
-
- /**
- * Reads chunks before first IDAT. Position before: after IDHR (crc included) Position after: just after the first
- * IDAT chunk id
- *
- * This can be called several times (tentatively), it does nothing if already run
- *
- * (Note: when should this be called? in the constructor? hardly, because we loose the opportunity to call
- * setChunkLoadBehaviour() and perhaps other settings before reading the first row? but sometimes we want to access
- * some metadata (plte, phys) before. Because of this, this method can be called explicitly but is also called
- * implicititly in some methods (getMetatada(), getChunks())
- *
- **/
- public void readFirstChunks() {
- if (!firstChunksNotYetRead())
- return;
- int clen = 0;
- boolean found = false;
- byte[] chunkid = new byte[4]; // it's important to reallocate in each iteration
- currentChunkGroup = ChunkList.CHUNK_GROUP_1_AFTERIDHR;
- while (!found) {
- clen = PngHelper.readInt4(is);
- offset += 4;
- if (clen < 0)
- break;
- PngHelper.readBytes(is, chunkid, 0, 4);
- offset += 4;
- if (Arrays.equals(chunkid, ChunkHelper.b_IDAT)) {
- found = true;
- currentChunkGroup = ChunkList.CHUNK_GROUP_4_IDAT;
- // add dummy idat chunk to list
- ChunkRaw chunk = new ChunkRaw(0, chunkid, false);
- addChunkToList(chunk);
- break;
- } else if (Arrays.equals(chunkid, ChunkHelper.b_IEND)) {
- throw new PngjInputException("END chunk found before image data (IDAT) at offset=" + offset);
- }
- ChunkRaw chunk = new ChunkRaw(clen, chunkid, true);
- String chunkids = ChunkHelper.toString(chunkid);
- boolean loadchunk = ChunkHelper.shouldLoad(chunkids, chunkLoadBehaviour);
- offset += chunk.readChunkData(is);
- if (chunkids.equals(ChunkHelper.PLTE))
- currentChunkGroup = ChunkList.CHUNK_GROUP_2_PLTE;
- if (loadchunk)
- addChunkToList(chunk);
- if (chunkids.equals(ChunkHelper.PLTE))
- currentChunkGroup = ChunkList.CHUNK_GROUP_3_AFTERPLTE;
- }
- int idatLen = found ? clen : -1;
- if (idatLen < 0)
- throw new PngjInputException("first idat chunk not found!");
- iIdatCstream = new PngIDatChunkInputStream(is, idatLen, offset);
- idatIstream = new InflaterInputStream(iIdatCstream);
- }
-
- /**
- * Reads (and processes) chunks after last IDAT.
- **/
- private void readLastChunks() {
- // PngHelper.logdebug("idat ended? " + iIdatCstream.isEnded());
- currentChunkGroup = ChunkList.CHUNK_GROUP_5_AFTERIDAT;
- if (!iIdatCstream.isEnded())
- iIdatCstream.forceChunkEnd();
- int clen = iIdatCstream.getLenLastChunk();
- byte[] chunkid = iIdatCstream.getIdLastChunk();
- boolean endfound = false;
- boolean first = true;
- boolean ignore = false;
- while (!endfound) {
- ignore = false;
- if (!first) {
- clen = PngHelper.readInt4(is);
- offset += 4;
- if (clen < 0)
- throw new PngjInputException("bad len " + clen);
- PngHelper.readBytes(is, chunkid, 0, 4);
- offset += 4;
- }
- first = false;
- if (Arrays.equals(chunkid, ChunkHelper.b_IDAT)) {
- // PngHelper.logdebug("extra IDAT chunk len - ignoring : ");
- ignore = true;
- } else if (Arrays.equals(chunkid, ChunkHelper.b_IEND)) {
- currentChunkGroup = ChunkList.CHUNK_GROUP_6_END;
- endfound = true;
- }
- ChunkRaw chunk = new ChunkRaw(clen, chunkid, true);
- String chunkids = ChunkHelper.toString(chunkid);
- boolean loadchunk = ChunkHelper.shouldLoad(chunkids, chunkLoadBehaviour);
- offset += chunk.readChunkData(is);
- if (loadchunk && !ignore) {
- addChunkToList(chunk);
- }
- }
- if (!endfound)
- throw new PngjInputException("end chunk not found - offset=" + offset);
- // PngHelper.logdebug("end chunk found ok offset=" + offset);
- }
-
- /**
- * Calls <code>readRow(int[] buffer, int nrow)</code> using internal ImageLine as buffer. This doesn't allocate or
- * copy anything.
- *
- * @return The ImageLine that also is available inside this object.
- */
- public ImageLine readRow(int nrow) {
- readRow(imgLine.scanline, nrow);
- imgLine.filterUsed = FilterType.getByVal(rowbfilter[0]);
- imgLine.setRown(nrow);
- return imgLine;
- }
-
- /**
- * Reads a line and returns it as a int[] array.
- *
- * You can pass (optionally) a prealocatted buffer.
- *
- * @param buffer
- * Prealocated buffer, or null.
- * @param nrow
- * Row number (0 is top). This is mostly for checking, because this library reads rows in sequence.
- *
- * @return The scanline in the same passwd buffer if it was allocated, a newly allocated one otherwise
- */
- public int[] readRow(int[] buffer, int nrow) {
- if (nrow < 0 || nrow >= imgInfo.rows)
- throw new PngjInputException("invalid line");
- if (nrow != rowNum + 1)
- throw new PngjInputException("invalid line (expected: " + (rowNum + 1));
- if (nrow == 0 && firstChunksNotYetRead())
- readFirstChunks();
- rowNum++;
- if (buffer == null || buffer.length < imgInfo.samplesPerRowP)
- buffer = new int[imgInfo.samplesPerRowP];
- // swap
- byte[] tmp = rowb;
- rowb = rowbprev;
- rowbprev = tmp;
- // loads in rowbfilter "raw" bytes, with filter
- PngHelper.readBytes(idatIstream, rowbfilter, 0, rowbfilter.length);
- rowb[0] = 0;
- unfilterRow();
- rowb[0] = rowbfilter[0];
- convertRowFromBytes(buffer);
- return buffer;
- }
-
- /**
- * This should be called after having read the last line. It reads extra chunks after IDAT, if present.
- */
- public void end() {
- offset = (int) iIdatCstream.getOffset();
- try {
- idatIstream.close();
- } catch (Exception e) {
- }
- readLastChunks();
- try {
- is.close();
- } catch (Exception e) {
- throw new PngjInputException("error closing input stream!", e);
- }
- }
-
- private void convertRowFromBytes(int[] buffer) {
- // http://www.libpng.org/pub/png/spec/1.2/PNG-DataRep.html
- int i, j;
- if (imgInfo.bitDepth <= 8) {
- for (i = 0, j = 1; i < imgInfo.samplesPerRowP; i++) {
- buffer[i] = (rowb[j++] & 0xFF);
- }
- } else { // 16 bitspc
- for (i = 0, j = 1; i < imgInfo.samplesPerRowP; i++) {
- buffer[i] = ((rowb[j++] & 0xFF) << 8) + (rowb[j++] & 0xFF);
- }
- }
- }
-
- private void unfilterRow() {
- int ftn = rowbfilter[0];
- FilterType ft = FilterType.getByVal(ftn);
- if (ft == null)
- throw new PngjInputException("Filter type " + ftn + " invalid");
- switch (ft) {
- case FILTER_NONE:
- unfilterRowNone();
- break;
- case FILTER_SUB:
- unfilterRowSub();
- break;
- case FILTER_UP:
- unfilterRowUp();
- break;
- case FILTER_AVERAGE:
- unfilterRowAverage();
- break;
- case FILTER_PAETH:
- unfilterRowPaeth();
- break;
- default:
- throw new PngjInputException("Filter type " + ftn + " not implemented");
- }
- }
-
- private void unfilterRowNone() {
- for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
- rowb[i] = (byte) (rowbfilter[i]);
- }
- }
-
- private void unfilterRowSub() {
- int i, j;
- for (i = 1; i <= imgInfo.bytesPixel; i++) {
- rowb[i] = (byte) (rowbfilter[i]);
- }
- for (j = 1, i = imgInfo.bytesPixel + 1; i <= imgInfo.bytesPerRow; i++, j++) {
- rowb[i] = (byte) (rowbfilter[i] + rowb[j]);
- }
- }
-
- private void unfilterRowUp() {
- for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
- rowb[i] = (byte) (rowbfilter[i] + rowbprev[i]);
- }
- }
-
- private void unfilterRowAverage() {
- int i, j, x;
- for (j = 1 - imgInfo.bytesPixel, i = 1; i <= imgInfo.bytesPerRow; i++, j++) {
- x = j > 0 ? (rowb[j] & 0xff) : 0;
- rowb[i] = (byte) (rowbfilter[i] + (x + (rowbprev[i] & 0xFF)) / 2);
- }
- }
-
- private void unfilterRowPaeth() {
- int i, j, x, y;
- for (j = 1 - imgInfo.bytesPixel, i = 1; i <= imgInfo.bytesPerRow; i++, j++) {
- x = j > 0 ? (rowb[j] & 0xFF) : 0;
- y = j > 0 ? (rowbprev[j] & 0xFF) : 0;
- rowb[i] = (byte) (rowbfilter[i] + FilterType.filterPaethPredictor(x, rowbprev[i] & 0xFF, y));
- }
- }
-
- public ChunkLoadBehaviour getChunkLoadBehaviour() {
- return chunkLoadBehaviour;
- }
-
- public void setChunkLoadBehaviour(ChunkLoadBehaviour chunkLoadBehaviour) {
- this.chunkLoadBehaviour = chunkLoadBehaviour;
- }
-
- private boolean firstChunksNotYetRead() {
- return currentChunkGroup < ChunkList.CHUNK_GROUP_1_AFTERIDHR;
- }
-
- public ChunkList getChunksList() {
- if (firstChunksNotYetRead())
- readFirstChunks();
- return chunksList;
- }
-
- public PngMetadata getMetadata() {
- if (firstChunksNotYetRead())
- readFirstChunks();
- return metadata;
- }
-
- public String toString() { // basic info
- return "filename=" + filename + " " + imgInfo.toString();
- }
-
-}
+package jogamp.opengl.util.pngj;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.zip.CRC32;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+import jogamp.opengl.util.pngj.ImageLine.SampleType;
+import jogamp.opengl.util.pngj.chunks.ChunkHelper;
+import jogamp.opengl.util.pngj.chunks.ChunkLoadBehaviour;
+import jogamp.opengl.util.pngj.chunks.ChunkRaw;
+import jogamp.opengl.util.pngj.chunks.ChunksList;
+import jogamp.opengl.util.pngj.chunks.PngChunk;
+import jogamp.opengl.util.pngj.chunks.PngChunkIDAT;
+import jogamp.opengl.util.pngj.chunks.PngChunkIHDR;
+import jogamp.opengl.util.pngj.chunks.PngChunkSkipped;
+import jogamp.opengl.util.pngj.chunks.PngMetadata;
+
+/**
+ * Reads a PNG image, line by line.
+ * <p>
+ * The reading sequence is as follows: <br>
+ * 1. At construction time, the header and IHDR chunk are read (basic image
+ * info) <br>
+ * 2. Afterwards you can set some additional global options. Eg.
+ * {@link #setUnpackedMode(boolean)}, {@link #setCrcCheckDisabled()}.<br>
+ * 3. Optional: If you call getMetadata() or getChunksLisk() before start
+ * reading the rows, all the chunks before IDAT are automatically loaded and
+ * available <br>
+ * 4a. The rows are read onen by one of the <tt>readRowXXX</tt> methods:
+ * {@link #readRowInt(int)}, {@link PngReader#readRowByte(int)}, etc, in order,
+ * from 0 to nrows-1 (you can skip or repeat rows, but not go backwards)<br>
+ * 4b. Alternatively, you can read all rows, or a subset, in a single call:
+ * {@link #readRowsInt()}, {@link #readRowsByte()} ,etc. In general this
+ * consumes more memory, but for interlaced images this is equally efficient,
+ * and more so if reading a small subset of rows.<br>
+ * 5. Read of the last row auyomatically loads the trailing chunks, and ends the
+ * reader.<br>
+ * 6. end() forcibly finishes/aborts the reading and closes the stream
+ */
+public class PngReader {
+
+ /**
+ * Basic image info - final and inmutable.
+ */
+ public final ImageInfo imgInfo;
+ /**
+ * not necesarily a filename, can be a description - merely informative
+ */
+ protected final String filename;
+ private ChunkLoadBehaviour chunkLoadBehaviour = ChunkLoadBehaviour.LOAD_CHUNK_ALWAYS; // see setter/getter
+ private boolean shouldCloseStream = true; // true: closes stream after ending - see setter/getter
+ // some performance/defensive limits
+ private long maxTotalBytesRead = 200 * 1024 * 1024; // 200MB
+ private int maxBytesMetadata = 5 * 1024 * 1024; // for ancillary chunks - see setter/getter
+ private int skipChunkMaxSize = 2 * 1024 * 1024; // chunks exceeding this size will be skipped (nor even CRC checked)
+ private String[] skipChunkIds = { "fdAT" }; // chunks with these ids will be skipped (nor even CRC checked)
+ private HashSet<String> skipChunkIdsSet; // lazily created from skipChunksById
+ protected final PngMetadata metadata; // this a wrapper over chunks
+ protected final ChunksList chunksList;
+ protected ImageLine imgLine;
+ // line as bytes, counting from 1 (index 0 is reserved for filter type)
+ protected final int buffersLen; // nominal length is imgInfo.bytesPerRow + 1 but it can be larger
+ protected byte[] rowb = null;
+ protected byte[] rowbprev = null; // rowb previous
+ protected byte[] rowbfilter = null; // current line 'filtered': exactly as in uncompressed stream
+ // only set for interlaced PNG
+ private final boolean interlaced;
+ private final PngDeinterlacer deinterlacer;
+ private boolean crcEnabled = true;
+ // this only influences the 1-2-4 bitdepth format
+ private boolean unpackedMode = false;
+ private Inflater inflater = null; // can be reused among several objects. see reuseBuffersFrom()
+ /**
+ * Current chunk group, (0-6) already read or reading
+ * <p>
+ * see {@link ChunksList}
+ */
+ protected int currentChunkGroup = -1;
+ protected int rowNum = -1; // last read row number, starting from 0
+ private long offset = 0; // offset in InputStream = bytes read
+ private int bytesChunksLoaded; // bytes loaded from anciallary chunks
+ protected final InputStream inputStream;
+ protected InflaterInputStream idatIstream;
+ protected PngIDatChunkInputStream iIdatCstream;
+ protected CRC32 crctest; // If set to non null, it gets a CRC of the unfiltered bytes, to check for images equality
+
+ /**
+ * Constructs a PngReader from an InputStream.
+ * <p>
+ * See also <code>FileHelper.createPngReader(File f)</code> if available.
+ *
+ * Reads only the signature and first chunk (IDHR)
+ *
+ * @param filenameOrDescription
+ * : Optional, can be a filename or a description. Just for
+ * error/debug messages
+ *
+ */
+ public PngReader(final InputStream inputStream, final String filenameOrDescription) {
+ this.filename = filenameOrDescription == null ? "" : filenameOrDescription;
+ this.inputStream = inputStream;
+ this.chunksList = new ChunksList(null);
+ this.metadata = new PngMetadata(chunksList);
+ // starts reading: signature
+ final byte[] pngid = new byte[8];
+ PngHelperInternal.readBytes(inputStream, pngid, 0, pngid.length);
+ offset += pngid.length;
+ if (!Arrays.equals(pngid, PngHelperInternal.getPngIdSignature()))
+ throw new PngjInputException("Bad PNG signature");
+ // reads first chunk
+ currentChunkGroup = ChunksList.CHUNK_GROUP_0_IDHR;
+ final int clen = PngHelperInternal.readInt4(inputStream);
+ offset += 4;
+ if (clen != 13)
+ throw new PngjInputException("IDHR chunk len != 13 ?? " + clen);
+ final byte[] chunkid = new byte[4];
+ PngHelperInternal.readBytes(inputStream, chunkid, 0, 4);
+ if (!Arrays.equals(chunkid, ChunkHelper.b_IHDR))
+ throw new PngjInputException("IHDR not found as first chunk??? [" + ChunkHelper.toString(chunkid) + "]");
+ offset += 4;
+ final PngChunkIHDR ihdr = (PngChunkIHDR) readChunk(chunkid, clen, false);
+ final boolean alpha = (ihdr.getColormodel() & 0x04) != 0;
+ final boolean palette = (ihdr.getColormodel() & 0x01) != 0;
+ final boolean grayscale = (ihdr.getColormodel() == 0 || ihdr.getColormodel() == 4);
+ // creates ImgInfo and imgLine, and allocates buffers
+ imgInfo = new ImageInfo(ihdr.getCols(), ihdr.getRows(), ihdr.getBitspc(), alpha, grayscale, palette);
+ interlaced = ihdr.getInterlaced() == 1;
+ deinterlacer = interlaced ? new PngDeinterlacer(imgInfo) : null;
+ buffersLen = imgInfo.bytesPerRow + 1;
+ // some checks
+ if (ihdr.getFilmeth() != 0 || ihdr.getCompmeth() != 0 || (ihdr.getInterlaced() & 0xFFFE) != 0)
+ throw new PngjInputException("compression method o filter method or interlaced unrecognized ");
+ if (ihdr.getColormodel() < 0 || ihdr.getColormodel() > 6 || ihdr.getColormodel() == 1
+ || ihdr.getColormodel() == 5)
+ throw new PngjInputException("Invalid colormodel " + ihdr.getColormodel());
+ if (ihdr.getBitspc() != 1 && ihdr.getBitspc() != 2 && ihdr.getBitspc() != 4 && ihdr.getBitspc() != 8
+ && ihdr.getBitspc() != 16)
+ throw new PngjInputException("Invalid bit depth " + ihdr.getBitspc());
+ }
+
+ private boolean firstChunksNotYetRead() {
+ return currentChunkGroup < ChunksList.CHUNK_GROUP_1_AFTERIDHR;
+ }
+
+ private void allocateBuffers() { // only if needed
+ if (rowbfilter == null || rowbfilter.length < buffersLen) {
+ rowbfilter = new byte[buffersLen];
+ rowb = new byte[buffersLen];
+ rowbprev = new byte[buffersLen];
+ }
+ }
+
+ /**
+ * Reads last Internally called after having read the last line. It reads
+ * extra chunks after IDAT, if present.
+ */
+ private void readLastAndClose() {
+ // offset = iIdatCstream.getOffset();
+ if (currentChunkGroup < ChunksList.CHUNK_GROUP_5_AFTERIDAT) {
+ try {
+ idatIstream.close();
+ } catch (final Exception e) {
+ }
+ readLastChunks();
+ }
+ close();
+ }
+
+ private void close() {
+ if (currentChunkGroup < ChunksList.CHUNK_GROUP_6_END) { // this could only happen if forced close
+ try {
+ idatIstream.close();
+ } catch (final Exception e) {
+ }
+ currentChunkGroup = ChunksList.CHUNK_GROUP_6_END;
+ }
+ if (shouldCloseStream) {
+ try {
+ inputStream.close();
+ } catch (final Exception e) {
+ throw new PngjInputException("error closing input stream!", e);
+ }
+ }
+ }
+
+ // nbytes: NOT including the filter byte. leaves result in rowb
+ private void unfilterRow(final int nbytes) {
+ final int ftn = rowbfilter[0];
+ final FilterType ft = FilterType.getByVal(ftn);
+ if (ft == null)
+ throw new PngjInputException("Filter type " + ftn + " invalid");
+ switch (ft) {
+ case FILTER_NONE:
+ unfilterRowNone(nbytes);
+ break;
+ case FILTER_SUB:
+ unfilterRowSub(nbytes);
+ break;
+ case FILTER_UP:
+ unfilterRowUp(nbytes);
+ break;
+ case FILTER_AVERAGE:
+ unfilterRowAverage(nbytes);
+ break;
+ case FILTER_PAETH:
+ unfilterRowPaeth(nbytes);
+ break;
+ default:
+ throw new PngjInputException("Filter type " + ftn + " not implemented");
+ }
+ if (crctest != null)
+ crctest.update(rowb, 1, buffersLen - 1);
+ }
+
+ private void unfilterRowAverage(final int nbytes) {
+ int i, j, x;
+ for (j = 1 - imgInfo.bytesPixel, i = 1; i <= nbytes; i++, j++) {
+ x = j > 0 ? (rowb[j] & 0xff) : 0;
+ rowb[i] = (byte) (rowbfilter[i] + (x + (rowbprev[i] & 0xFF)) / 2);
+ }
+ }
+
+ private void unfilterRowNone(final int nbytes) {
+ for (int i = 1; i <= nbytes; i++) {
+ rowb[i] = (rowbfilter[i]);
+ }
+ }
+
+ private void unfilterRowPaeth(final int nbytes) {
+ int i, j, x, y;
+ for (j = 1 - imgInfo.bytesPixel, i = 1; i <= nbytes; i++, j++) {
+ x = j > 0 ? (rowb[j] & 0xFF) : 0;
+ y = j > 0 ? (rowbprev[j] & 0xFF) : 0;
+ rowb[i] = (byte) (rowbfilter[i] + PngHelperInternal.filterPaethPredictor(x, rowbprev[i] & 0xFF, y));
+ }
+ }
+
+ private void unfilterRowSub(final int nbytes) {
+ int i, j;
+ for (i = 1; i <= imgInfo.bytesPixel; i++) {
+ rowb[i] = (rowbfilter[i]);
+ }
+ for (j = 1, i = imgInfo.bytesPixel + 1; i <= nbytes; i++, j++) {
+ rowb[i] = (byte) (rowbfilter[i] + rowb[j]);
+ }
+ }
+
+ private void unfilterRowUp(final int nbytes) {
+ for (int i = 1; i <= nbytes; i++) {
+ rowb[i] = (byte) (rowbfilter[i] + rowbprev[i]);
+ }
+ }
+
+ /**
+ * Reads chunks before first IDAT. Normally this is called automatically
+ * <p>
+ * Position before: after IDHR (crc included) Position after: just after the
+ * first IDAT chunk id
+ * <P>
+ * This can be called several times (tentatively), it does nothing if
+ * already run
+ * <p>
+ * (Note: when should this be called? in the constructor? hardly, because we
+ * loose the opportunity to call setChunkLoadBehaviour() and perhaps other
+ * settings before reading the first row? but sometimes we want to access
+ * some metadata (plte, phys) before. Because of this, this method can be
+ * called explicitly but is also called implicititly in some methods
+ * (getMetatada(), getChunksList())
+ */
+ private final void readFirstChunks() {
+ if (!firstChunksNotYetRead())
+ return;
+ int clen = 0;
+ boolean found = false;
+ final byte[] chunkid = new byte[4]; // it's important to reallocate in each iteration
+ currentChunkGroup = ChunksList.CHUNK_GROUP_1_AFTERIDHR;
+ while (!found) {
+ clen = PngHelperInternal.readInt4(inputStream);
+ offset += 4;
+ if (clen < 0)
+ break;
+ PngHelperInternal.readBytes(inputStream, chunkid, 0, 4);
+ offset += 4;
+ if (Arrays.equals(chunkid, ChunkHelper.b_IDAT)) {
+ found = true;
+ currentChunkGroup = ChunksList.CHUNK_GROUP_4_IDAT;
+ // add dummy idat chunk to list
+ chunksList.appendReadChunk(new PngChunkIDAT(imgInfo, clen, offset - 8), currentChunkGroup);
+ break;
+ } else if (Arrays.equals(chunkid, ChunkHelper.b_IEND)) {
+ throw new PngjInputException("END chunk found before image data (IDAT) at offset=" + offset);
+ }
+ if (Arrays.equals(chunkid, ChunkHelper.b_PLTE))
+ currentChunkGroup = ChunksList.CHUNK_GROUP_2_PLTE;
+ readChunk(chunkid, clen, false);
+ if (Arrays.equals(chunkid, ChunkHelper.b_PLTE))
+ currentChunkGroup = ChunksList.CHUNK_GROUP_3_AFTERPLTE;
+ }
+ final int idatLen = found ? clen : -1;
+ if (idatLen < 0)
+ throw new PngjInputException("first idat chunk not found!");
+ iIdatCstream = new PngIDatChunkInputStream(inputStream, idatLen, offset);
+ if(inflater == null) {
+ inflater = new Inflater();
+ } else {
+ inflater.reset();
+ }
+ idatIstream = new InflaterInputStream(iIdatCstream, inflater);
+ if (!crcEnabled)
+ iIdatCstream.disableCrcCheck();
+ }
+
+ /**
+ * Reads (and processes) chunks after last IDAT.
+ **/
+ void readLastChunks() {
+ // PngHelper.logdebug("idat ended? " + iIdatCstream.isEnded());
+ currentChunkGroup = ChunksList.CHUNK_GROUP_5_AFTERIDAT;
+ if (!iIdatCstream.isEnded())
+ iIdatCstream.forceChunkEnd();
+ int clen = iIdatCstream.getLenLastChunk();
+ final byte[] chunkid = iIdatCstream.getIdLastChunk();
+ boolean endfound = false;
+ boolean first = true;
+ boolean skip = false;
+ while (!endfound) {
+ skip = false;
+ if (!first) {
+ clen = PngHelperInternal.readInt4(inputStream);
+ offset += 4;
+ if (clen < 0)
+ throw new PngjInputException("bad chuck len " + clen);
+ PngHelperInternal.readBytes(inputStream, chunkid, 0, 4);
+ offset += 4;
+ }
+ first = false;
+ if (Arrays.equals(chunkid, ChunkHelper.b_IDAT)) {
+ skip = true; // extra dummy (empty?) idat chunk, it can happen, ignore it
+ } else if (Arrays.equals(chunkid, ChunkHelper.b_IEND)) {
+ currentChunkGroup = ChunksList.CHUNK_GROUP_6_END;
+ endfound = true;
+ }
+ readChunk(chunkid, clen, skip);
+ }
+ if (!endfound)
+ throw new PngjInputException("end chunk not found - offset=" + offset);
+ // PngHelper.logdebug("end chunk found ok offset=" + offset);
+ }
+
+ /**
+ * Reads chunkd from input stream, adds to ChunksList, and returns it. If
+ * it's skipped, a PngChunkSkipped object is created
+ */
+ private PngChunk readChunk(final byte[] chunkid, final int clen, final boolean skipforced) {
+ if (clen < 0)
+ throw new PngjInputException("invalid chunk lenght: " + clen);
+ // skipChunksByIdSet is created lazyly, if fist IHDR has already been read
+ if (skipChunkIdsSet == null && currentChunkGroup > ChunksList.CHUNK_GROUP_0_IDHR)
+ skipChunkIdsSet = new HashSet<String>(Arrays.asList(skipChunkIds));
+ final String chunkidstr = ChunkHelper.toString(chunkid);
+ final boolean critical = ChunkHelper.isCritical(chunkidstr);
+ PngChunk pngChunk = null;
+ boolean skip = skipforced;
+ if (maxTotalBytesRead > 0 && clen + offset > maxTotalBytesRead)
+ throw new PngjInputException("Maximum total bytes to read exceeeded: " + maxTotalBytesRead + " offset:"
+ + offset + " clen=" + clen);
+ // an ancillary chunks can be skipped because of several reasons:
+ if (currentChunkGroup > ChunksList.CHUNK_GROUP_0_IDHR && !critical)
+ skip = skip || (skipChunkMaxSize > 0 && clen >= skipChunkMaxSize) || skipChunkIdsSet.contains(chunkidstr)
+ || (maxBytesMetadata > 0 && clen > maxBytesMetadata - bytesChunksLoaded)
+ || !ChunkHelper.shouldLoad(chunkidstr, chunkLoadBehaviour);
+ if (skip) {
+ PngHelperInternal.skipBytes(inputStream, clen);
+ PngHelperInternal.readInt4(inputStream); // skip - we dont call PngHelperInternal.skipBytes(inputStream,
+ // clen + 4) for risk of overflow
+ pngChunk = new PngChunkSkipped(chunkidstr, imgInfo, clen);
+ } else {
+ final ChunkRaw chunk = new ChunkRaw(clen, chunkid, true);
+ chunk.readChunkData(inputStream, crcEnabled || critical);
+ pngChunk = PngChunk.factory(chunk, imgInfo);
+ if (!pngChunk.crit)
+ bytesChunksLoaded += chunk.len;
+ }
+ pngChunk.setOffset(offset - 8L);
+ chunksList.appendReadChunk(pngChunk, currentChunkGroup);
+ offset += clen + 4L;
+ return pngChunk;
+ }
+
+ /**
+ * Logs/prints a warning.
+ * <p>
+ * The default behaviour is print to stderr, but it can be overriden.
+ * <p>
+ * This happens rarely - most errors are fatal.
+ */
+ protected void logWarn(final String warn) {
+ System.err.println(warn);
+ }
+
+ /**
+ * @see #setChunkLoadBehaviour(ChunkLoadBehaviour)
+ */
+ public ChunkLoadBehaviour getChunkLoadBehaviour() {
+ return chunkLoadBehaviour;
+ }
+
+ /**
+ * Determines which ancillary chunks (metada) are to be loaded
+ *
+ * @param chunkLoadBehaviour
+ * {@link ChunkLoadBehaviour}
+ */
+ public void setChunkLoadBehaviour(final ChunkLoadBehaviour chunkLoadBehaviour) {
+ this.chunkLoadBehaviour = chunkLoadBehaviour;
+ }
+
+ /**
+ * All loaded chunks (metada). If we have not yet end reading the image,
+ * this will include only the chunks before the pixels data (IDAT)
+ * <p>
+ * Critical chunks are included, except that all IDAT chunks appearance are
+ * replaced by a single dummy-marker IDAT chunk. These might be copied to
+ * the PngWriter
+ * <p>
+ *
+ * @see #getMetadata()
+ */
+ public ChunksList getChunksList() {
+ if (firstChunksNotYetRead())
+ readFirstChunks();
+ return chunksList;
+ }
+
+ int getCurrentChunkGroup() {
+ return currentChunkGroup;
+ }
+
+ /**
+ * High level wrapper over chunksList
+ *
+ * @see #getChunksList()
+ */
+ public PngMetadata getMetadata() {
+ if (firstChunksNotYetRead())
+ readFirstChunks();
+ return metadata;
+ }
+
+ /**
+ * If called for first time, calls readRowInt. Elsewhere, it calls the
+ * appropiate readRowInt/readRowByte
+ * <p>
+ * In general, specifying the concrete readRowInt/readRowByte is preferrable
+ *
+ * @see #readRowInt(int) {@link #readRowByte(int)}
+ */
+ public ImageLine readRow(final int nrow) {
+ if (imgLine == null)
+ imgLine = new ImageLine(imgInfo, SampleType.INT, unpackedMode);
+ return imgLine.sampleType != SampleType.BYTE ? readRowInt(nrow) : readRowByte(nrow);
+ }
+
+ /**
+ * Reads the row as INT, storing it in the {@link #imgLine} property and
+ * returning it.
+ *
+ * The row must be greater or equal than the last read row.
+ *
+ * @param nrow
+ * Row number, from 0 to rows-1. Increasing order.
+ * @return ImageLine object, also available as field. Data is in
+ * {@link ImageLine#scanline} (int) field.
+ */
+ public ImageLine readRowInt(final int nrow) {
+ if (imgLine == null)
+ imgLine = new ImageLine(imgInfo, SampleType.INT, unpackedMode);
+ if (imgLine.getRown() == nrow) // already read
+ return imgLine;
+ readRowInt(imgLine.scanline, nrow);
+ imgLine.setFilterUsed(FilterType.getByVal(rowbfilter[0]));
+ imgLine.setRown(nrow);
+ return imgLine;
+ }
+
+ /**
+ * Reads the row as BYTES, storing it in the {@link #imgLine} property and
+ * returning it.
+ *
+ * The row must be greater or equal than the last read row. This method
+ * allows to pass the same row that was last read.
+ *
+ * @param nrow
+ * Row number, from 0 to rows-1. Increasing order.
+ * @return ImageLine object, also available as field. Data is in
+ * {@link ImageLine#scanlineb} (byte) field.
+ */
+ public ImageLine readRowByte(final int nrow) {
+ if (imgLine == null)
+ imgLine = new ImageLine(imgInfo, SampleType.BYTE, unpackedMode);
+ if (imgLine.getRown() == nrow) // already read
+ return imgLine;
+ readRowByte(imgLine.scanlineb, nrow);
+ imgLine.setFilterUsed(FilterType.getByVal(rowbfilter[0]));
+ imgLine.setRown(nrow);
+ return imgLine;
+ }
+
+ /**
+ * @see #readRowInt(int[], int)
+ */
+ public final int[] readRow(final int[] buffer, final int nrow) {
+ return readRowInt(buffer, nrow);
+ }
+
+ /**
+ * Reads a line and returns it as a int[] array.
+ * <p>
+ * You can pass (optionally) a prealocatted buffer.
+ * <p>
+ * If the bitdepth is less than 8, the bytes are packed - unless
+ * {@link #unpackedMode} is true.
+ *
+ * @param buffer
+ * Prealocated buffer, or null.
+ * @param nrow
+ * Row number (0 is top). Most be strictly greater than the last
+ * read row.
+ *
+ * @return The scanline in the same passwd buffer if it was allocated, a
+ * newly allocated one otherwise
+ */
+ public final int[] readRowInt(int[] buffer, final int nrow) {
+ if (buffer == null)
+ buffer = new int[unpackedMode ? imgInfo.samplesPerRow : imgInfo.samplesPerRowPacked];
+ if (!interlaced) {
+ if (nrow <= rowNum)
+ throw new PngjInputException("rows must be read in increasing order: " + nrow);
+ int bytesread = 0;
+ while (rowNum < nrow)
+ bytesread = readRowRaw(rowNum + 1); // read rows, perhaps skipping if necessary
+ decodeLastReadRowToInt(buffer, bytesread);
+ } else { // interlaced
+ if (deinterlacer.getImageInt() == null)
+ deinterlacer.setImageInt(readRowsInt().scanlines); // read all image and store it in deinterlacer
+ System.arraycopy(deinterlacer.getImageInt()[nrow], 0, buffer, 0, unpackedMode ? imgInfo.samplesPerRow
+ : imgInfo.samplesPerRowPacked);
+ }
+ return buffer;
+ }
+
+ /**
+ * Reads a line and returns it as a byte[] array.
+ * <p>
+ * You can pass (optionally) a prealocatted buffer.
+ * <p>
+ * If the bitdepth is less than 8, the bytes are packed - unless
+ * {@link #unpackedMode} is true. <br>
+ * If the bitdepth is 16, the least significant byte is lost.
+ * <p>
+ *
+ * @param buffer
+ * Prealocated buffer, or null.
+ * @param nrow
+ * Row number (0 is top). Most be strictly greater than the last
+ * read row.
+ *
+ * @return The scanline in the same passwd buffer if it was allocated, a
+ * newly allocated one otherwise
+ */
+ public final byte[] readRowByte(byte[] buffer, final int nrow) {
+ if (buffer == null)
+ buffer = new byte[unpackedMode ? imgInfo.samplesPerRow : imgInfo.samplesPerRowPacked];
+ if (!interlaced) {
+ if (nrow <= rowNum)
+ throw new PngjInputException("rows must be read in increasing order: " + nrow);
+ int bytesread = 0;
+ while (rowNum < nrow)
+ bytesread = readRowRaw(rowNum + 1); // read rows, perhaps skipping if necessary
+ decodeLastReadRowToByte(buffer, bytesread);
+ } else { // interlaced
+ if (deinterlacer.getImageByte() == null)
+ deinterlacer.setImageByte(readRowsByte().scanlinesb); // read all image and store it in deinterlacer
+ System.arraycopy(deinterlacer.getImageByte()[nrow], 0, buffer, 0, unpackedMode ? imgInfo.samplesPerRow
+ : imgInfo.samplesPerRowPacked);
+ }
+ return buffer;
+ }
+
+ /**
+ * @param nrow
+ * @deprecated Now {@link #readRow(int)} implements the same funcion. This
+ * method will be removed in future releases
+ */
+ public ImageLine getRow(final int nrow) {
+ return readRow(nrow);
+ }
+
+ private void decodeLastReadRowToInt(final int[] buffer, final int bytesRead) {
+ if (imgInfo.bitDepth <= 8)
+ for (int i = 0, j = 1; i < bytesRead; i++)
+ buffer[i] = (rowb[j++] & 0xFF); // http://www.libpng.org/pub/png/spec/1.2/PNG-DataRep.html
+ else
+ for (int i = 0, j = 1; j <= bytesRead; i++)
+ buffer[i] = ((rowb[j++] & 0xFF) << 8) + (rowb[j++] & 0xFF); // 16 bitspc
+ if (imgInfo.packed && unpackedMode)
+ ImageLine.unpackInplaceInt(imgInfo, buffer, buffer, false);
+ }
+
+ private void decodeLastReadRowToByte(final byte[] buffer, final int bytesRead) {
+ if (imgInfo.bitDepth <= 8)
+ System.arraycopy(rowb, 1, buffer, 0, bytesRead);
+ else
+ for (int i = 0, j = 1; j < bytesRead; i++, j += 2)
+ buffer[i] = rowb[j];// 16 bits in 1 byte: this discards the LSB!!!
+ if (imgInfo.packed && unpackedMode)
+ ImageLine.unpackInplaceByte(imgInfo, buffer, buffer, false);
+ }
+
+ /**
+ * Reads a set of lines and returns it as a ImageLines object, which wraps
+ * matrix. Internally it reads all lines, but decodes and stores only the
+ * wanted ones. This starts and ends the reading, and cannot be combined
+ * with other reading methods.
+ * <p>
+ * This it's more efficient (speed an memory) that doing calling
+ * readRowInt() for each desired line only if the image is interlaced.
+ * <p>
+ * Notice that the columns in the matrix is not the pixel width of the
+ * image, but rather pixels x channels
+ *
+ * @see #readRowInt(int) to read about the format of each row
+ *
+ * @param rowOffset
+ * Number of rows to be skipped
+ * @param nRows
+ * Total number of rows to be read. -1: read all available
+ * @param rowStep
+ * Row increment. If 1, we read consecutive lines; if 2, we read
+ * even/odd lines, etc
+ * @return Set of lines as a ImageLines, which wraps a matrix
+ */
+ public ImageLines readRowsInt(final int rowOffset, int nRows, final int rowStep) {
+ if (nRows < 0)
+ nRows = (imgInfo.rows - rowOffset) / rowStep;
+ if (rowStep < 1 || rowOffset < 0 || nRows * rowStep + rowOffset > imgInfo.rows)
+ throw new PngjInputException("bad args");
+ final ImageLines imlines = new ImageLines(imgInfo, SampleType.INT, unpackedMode, rowOffset, nRows, rowStep);
+ if (!interlaced) {
+ for (int j = 0; j < imgInfo.rows; j++) {
+ final int bytesread = readRowRaw(j); // read and perhaps discards
+ final int mrow = imlines.imageRowToMatrixRowStrict(j);
+ if (mrow >= 0)
+ decodeLastReadRowToInt(imlines.scanlines[mrow], bytesread);
+ }
+ } else { // and now, for something completely different (interlaced)
+ final int[] buf = new int[unpackedMode ? imgInfo.samplesPerRow : imgInfo.samplesPerRowPacked];
+ for (int p = 1; p <= 7; p++) {
+ deinterlacer.setPass(p);
+ for (int i = 0; i < deinterlacer.getRows(); i++) {
+ final int bytesread = readRowRaw(i);
+ final int j = deinterlacer.getCurrRowReal();
+ final int mrow = imlines.imageRowToMatrixRowStrict(j);
+ if (mrow >= 0) {
+ decodeLastReadRowToInt(buf, bytesread);
+ deinterlacer.deinterlaceInt(buf, imlines.scanlines[mrow], !unpackedMode);
+ }
+ }
+ }
+ }
+ end();
+ return imlines;
+ }
+
+ /**
+ * Same as readRowsInt(0, imgInfo.rows, 1)
+ *
+ * @see #readRowsInt(int, int, int)
+ */
+ public ImageLines readRowsInt() {
+ return readRowsInt(0, imgInfo.rows, 1);
+ }
+
+ /**
+ * Reads a set of lines and returns it as a ImageLines object, which wrapas
+ * a byte[][] matrix. Internally it reads all lines, but decodes and stores
+ * only the wanted ones. This starts and ends the reading, and cannot be
+ * combined with other reading methods.
+ * <p>
+ * This it's more efficient (speed an memory) that doing calling
+ * readRowByte() for each desired line only if the image is interlaced.
+ * <p>
+ * Notice that the columns in the matrix is not the pixel width of the
+ * image, but rather pixels x channels
+ *
+ * @see #readRowByte(int) to read about the format of each row. Notice that
+ * if the bitdepth is 16 this will lose information
+ *
+ * @param rowOffset
+ * Number of rows to be skipped
+ * @param nRows
+ * Total number of rows to be read. -1: read all available
+ * @param rowStep
+ * Row increment. If 1, we read consecutive lines; if 2, we read
+ * even/odd lines, etc
+ * @return Set of lines as a matrix
+ */
+ public ImageLines readRowsByte(final int rowOffset, int nRows, final int rowStep) {
+ if (nRows < 0)
+ nRows = (imgInfo.rows - rowOffset) / rowStep;
+ if (rowStep < 1 || rowOffset < 0 || nRows * rowStep + rowOffset > imgInfo.rows)
+ throw new PngjInputException("bad args");
+ final ImageLines imlines = new ImageLines(imgInfo, SampleType.BYTE, unpackedMode, rowOffset, nRows, rowStep);
+ if (!interlaced) {
+ for (int j = 0; j < imgInfo.rows; j++) {
+ final int bytesread = readRowRaw(j); // read and perhaps discards
+ final int mrow = imlines.imageRowToMatrixRowStrict(j);
+ if (mrow >= 0)
+ decodeLastReadRowToByte(imlines.scanlinesb[mrow], bytesread);
+ }
+ } else { // and now, for something completely different (interlaced)
+ final byte[] buf = new byte[unpackedMode ? imgInfo.samplesPerRow : imgInfo.samplesPerRowPacked];
+ for (int p = 1; p <= 7; p++) {
+ deinterlacer.setPass(p);
+ for (int i = 0; i < deinterlacer.getRows(); i++) {
+ final int bytesread = readRowRaw(i);
+ final int j = deinterlacer.getCurrRowReal();
+ final int mrow = imlines.imageRowToMatrixRowStrict(j);
+ if (mrow >= 0) {
+ decodeLastReadRowToByte(buf, bytesread);
+ deinterlacer.deinterlaceByte(buf, imlines.scanlinesb[mrow], !unpackedMode);
+ }
+ }
+ }
+ }
+ end();
+ return imlines;
+ }
+
+ /**
+ * Same as readRowsByte(0, imgInfo.rows, 1)
+ *
+ * @see #readRowsByte(int, int, int)
+ */
+ public ImageLines readRowsByte() {
+ return readRowsByte(0, imgInfo.rows, 1);
+ }
+
+ /*
+ * For the interlaced case, nrow indicates the subsampled image - the pass must be set already.
+ *
+ * This must be called in strict order, both for interlaced or no interlaced.
+ *
+ * Updates rowNum.
+ *
+ * Leaves raw result in rowb
+ *
+ * Returns bytes actually read (not including the filter byte)
+ */
+ private int readRowRaw(final int nrow) {
+ if (nrow == 0) {
+ if (firstChunksNotYetRead())
+ readFirstChunks();
+ allocateBuffers();
+ if (interlaced)
+ Arrays.fill(rowb, (byte) 0); // new subimage: reset filters: this is enough, see the swap that happens lines
+ }
+ // below
+ int bytesRead = imgInfo.bytesPerRow; // NOT including the filter byte
+ if (interlaced) {
+ if (nrow < 0 || nrow > deinterlacer.getRows() || (nrow != 0 && nrow != deinterlacer.getCurrRowSubimg() + 1))
+ throw new PngjInputException("invalid row in interlaced mode: " + nrow);
+ deinterlacer.setRow(nrow);
+ bytesRead = (imgInfo.bitspPixel * deinterlacer.getPixelsToRead() + 7) / 8;
+ if (bytesRead < 1)
+ throw new PngjExceptionInternal("wtf??");
+ } else { // check for non interlaced
+ if (nrow < 0 || nrow >= imgInfo.rows || nrow != rowNum + 1)
+ throw new PngjInputException("invalid row: " + nrow);
+ }
+ rowNum = nrow;
+ // swap buffers
+ final byte[] tmp = rowb;
+ rowb = rowbprev;
+ rowbprev = tmp;
+ // loads in rowbfilter "raw" bytes, with filter
+ PngHelperInternal.readBytes(idatIstream, rowbfilter, 0, bytesRead + 1);
+ offset = iIdatCstream.getOffset();
+ if (offset < 0)
+ throw new PngjExceptionInternal("bad offset ??" + offset);
+ if (maxTotalBytesRead > 0 && offset >= maxTotalBytesRead)
+ throw new PngjInputException("Reading IDAT: Maximum total bytes to read exceeeded: " + maxTotalBytesRead
+ + " offset:" + offset);
+ rowb[0] = 0;
+ unfilterRow(bytesRead);
+ rowb[0] = rowbfilter[0];
+ if ((rowNum == imgInfo.rows - 1 && !interlaced) || (interlaced && deinterlacer.isAtLastRow()))
+ readLastAndClose();
+ return bytesRead;
+ }
+
+ /**
+ * Reads all the (remaining) file, skipping the pixels data. This is much
+ * more efficient that calling readRow(), specially for big files (about 10
+ * times faster!), because it doesn't even decompress the IDAT stream and
+ * disables CRC check Use this if you are not interested in reading
+ * pixels,only metadata.
+ */
+ public void readSkippingAllRows() {
+ if (firstChunksNotYetRead())
+ readFirstChunks();
+ // we read directly from the compressed stream, we dont decompress nor chec CRC
+ iIdatCstream.disableCrcCheck();
+ allocateBuffers();
+ try {
+ int r;
+ do {
+ r = iIdatCstream.read(rowbfilter, 0, buffersLen);
+ } while (r >= 0);
+ } catch (final IOException e) {
+ throw new PngjInputException("error in raw read of IDAT", e);
+ }
+ offset = iIdatCstream.getOffset();
+ if (offset < 0)
+ throw new PngjExceptionInternal("bad offset ??" + offset);
+ if (maxTotalBytesRead > 0 && offset >= maxTotalBytesRead)
+ throw new PngjInputException("Reading IDAT: Maximum total bytes to read exceeeded: " + maxTotalBytesRead
+ + " offset:" + offset);
+ readLastAndClose();
+ }
+
+ /**
+ * Set total maximum bytes to read (0: unlimited; default: 200MB). <br>
+ * These are the bytes read (not loaded) in the input stream. If exceeded,
+ * an exception will be thrown.
+ */
+ public void setMaxTotalBytesRead(final long maxTotalBytesToRead) {
+ this.maxTotalBytesRead = maxTotalBytesToRead;
+ }
+
+ /**
+ * @return Total maximum bytes to read.
+ */
+ public long getMaxTotalBytesRead() {
+ return maxTotalBytesRead;
+ }
+
+ /**
+ * Set total maximum bytes to load from ancillary chunks (0: unlimited;
+ * default: 5Mb).<br>
+ * If exceeded, some chunks will be skipped
+ */
+ public void setMaxBytesMetadata(final int maxBytesChunksToLoad) {
+ this.maxBytesMetadata = maxBytesChunksToLoad;
+ }
+
+ /**
+ * @return Total maximum bytes to load from ancillary ckunks.
+ */
+ public int getMaxBytesMetadata() {
+ return maxBytesMetadata;
+ }
+
+ /**
+ * Set maximum size in bytes for individual ancillary chunks (0: unlimited;
+ * default: 2MB). <br>
+ * Chunks exceeding this length will be skipped (the CRC will not be
+ * checked) and the chunk will be saved as a PngChunkSkipped object. See
+ * also setSkipChunkIds
+ */
+ public void setSkipChunkMaxSize(final int skipChunksBySize) {
+ this.skipChunkMaxSize = skipChunksBySize;
+ }
+
+ /**
+ * @return maximum size in bytes for individual ancillary chunks.
+ */
+ public int getSkipChunkMaxSize() {
+ return skipChunkMaxSize;
+ }
+
+ /**
+ * Chunks ids to be skipped. <br>
+ * These chunks will be skipped (the CRC will not be checked) and the chunk
+ * will be saved as a PngChunkSkipped object. See also setSkipChunkMaxSize
+ */
+ public void setSkipChunkIds(final String[] skipChunksById) {
+ this.skipChunkIds = skipChunksById == null ? new String[] {} : skipChunksById;
+ }
+
+ /**
+ * @return Chunk-IDs to be skipped.
+ */
+ public String[] getSkipChunkIds() {
+ return skipChunkIds;
+ }
+
+ /**
+ * if true, input stream will be closed after ending read
+ * <p>
+ * default=true
+ */
+ public void setShouldCloseStream(final boolean shouldCloseStream) {
+ this.shouldCloseStream = shouldCloseStream;
+ }
+
+ /**
+ * Normally this does nothing, but it can be used to force a premature
+ * closing. Its recommended practice to call it after reading the image
+ * pixels.
+ */
+ public void end() {
+ if (currentChunkGroup < ChunksList.CHUNK_GROUP_6_END)
+ close();
+ }
+
+ /**
+ * Interlaced PNG is accepted -though not welcomed- now...
+ */
+ public boolean isInterlaced() {
+ return interlaced;
+ }
+
+ /**
+ * set/unset "unpackedMode"<br>
+ * If false (default) packed types (bitdepth=1,2 or 4) will keep several
+ * samples packed in one element (byte or int) <br>
+ * If true, samples will be unpacked on reading, and each element in the
+ * scanline will be sample. This implies more processing and memory, but
+ * it's the most efficient option if you intend to read individual pixels. <br>
+ * This option should only be set before start reading.
+ *
+ * @param unPackedMode
+ */
+ public void setUnpackedMode(final boolean unPackedMode) {
+ this.unpackedMode = unPackedMode;
+ }
+
+ /**
+ * @see PngReader#setUnpackedMode(boolean)
+ */
+ public boolean isUnpackedMode() {
+ return unpackedMode;
+ }
+
+ /**
+ * Tries to reuse the allocated buffers from other already used PngReader
+ * object. This will have no effect if the buffers are smaller than necessary.
+ * It also reuses the inflater.
+ *
+ * @param other A PngReader that has already finished reading pixels. Can be null.
+ */
+ public void reuseBuffersFrom(final PngReader other) {
+ if(other==null) return;
+ if (other.currentChunkGroup < ChunksList.CHUNK_GROUP_5_AFTERIDAT)
+ throw new PngjInputException("PngReader to be reused have not yet ended reading pixels");
+ if (other.rowbfilter != null && other.rowbfilter.length >= buffersLen) {
+ rowbfilter = other.rowbfilter;
+ rowb = other.rowb;
+ rowbprev = other.rowbprev;
+ }
+ inflater = other.inflater;
+ }
+
+ /**
+ * Disables the CRC integrity check in IDAT chunks and ancillary chunks,
+ * this gives a slight increase in reading speed for big files
+ */
+ public void setCrcCheckDisabled() {
+ crcEnabled = false;
+ }
+
+ /**
+ * Just for testing. TO be called after ending reading, only if
+ * initCrctest() was called before start
+ *
+ * @return CRC of the raw pixels values
+ */
+ long getCrctestVal() {
+ return crctest.getValue();
+ }
+
+ /**
+ * Inits CRC object and enables CRC calculation
+ */
+ void initCrctest() {
+ this.crctest = new CRC32();
+ }
+
+ /**
+ * Basic info, for debugging.
+ */
+ @Override
+ public String toString() { // basic info
+ return "filename=" + filename + " " + imgInfo.toString();
+ }
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngWriter.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngWriter.java
index ee8472bf0..ed5dd7d69 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngWriter.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngWriter.java
@@ -7,54 +7,88 @@ import java.util.List;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
+import jogamp.opengl.util.pngj.ImageLine.SampleType;
import jogamp.opengl.util.pngj.chunks.ChunkCopyBehaviour;
import jogamp.opengl.util.pngj.chunks.ChunkHelper;
-import jogamp.opengl.util.pngj.chunks.ChunkList;
+import jogamp.opengl.util.pngj.chunks.ChunksList;
+import jogamp.opengl.util.pngj.chunks.ChunksListForWrite;
import jogamp.opengl.util.pngj.chunks.PngChunk;
import jogamp.opengl.util.pngj.chunks.PngChunkIEND;
import jogamp.opengl.util.pngj.chunks.PngChunkIHDR;
+import jogamp.opengl.util.pngj.chunks.PngChunkSkipped;
import jogamp.opengl.util.pngj.chunks.PngChunkTextVar;
import jogamp.opengl.util.pngj.chunks.PngMetadata;
-
/**
- * Writes a PNG image, line by line.
+ * Writes a PNG image
*/
public class PngWriter {
public final ImageInfo imgInfo;
- protected int compLevel = 6; // zip compression level 0 - 9
- private int deflaterStrategy = Deflater.FILTERED;
- protected FilterWriteStrategy filterStrat;
+ private final String filename; // optional, can be a description
+
+ /**
+ * last read row number, starting from 0
+ */
+ protected int rowNum = -1;
+ private final ChunksListForWrite chunksList;
+
+ private final PngMetadata metadata; // high level wrapper over chunkList
+
+ /**
+ * Current chunk grounp, (0-6) already read or reading
+ * <p>
+ * see {@link ChunksList}
+ */
protected int currentChunkGroup = -1;
- protected int rowNum = -1; // current line number
- // current line, one (packed) sample per element (layout differnt from rowb!)
- protected int[] scanline = null;
- protected byte[] rowb = null; // element 0 is filter type!
- protected byte[] rowbprev = null; // rowb prev
- protected byte[] rowbfilter = null; // current line with filter
+ /**
+ * PNG filter strategy
+ */
+ protected FilterWriteStrategy filterStrat;
- protected final OutputStream os;
- protected final String filename; // optional, can be a description
+ /**
+ * zip compression level 0 - 9
+ */
+ private int compLevel = 6;
+ private boolean shouldCloseStream = true; // true: closes stream after ending write
private PngIDatChunkOutputStream datStream;
+
private DeflaterOutputStream datStreamDeflated;
- private final ChunkList chunkList;
- private final PngMetadata metadata; // high level wrapper over chunkList
+ /**
+ * Deflate algortithm compression strategy
+ */
+ private int deflaterStrategy = Deflater.FILTERED;
+
+ private final int[] histox = new int[256]; // auxiliar buffer, only used by reportResultsForFilter
+
+ private int idatMaxSize = 0; // 0=use default (PngIDatChunkOutputStream 32768)
+
+ private final OutputStream os;
+
+ protected byte[] rowb = null; // element 0 is filter type!
+ protected byte[] rowbfilter = null; // current line with filter
+
+ protected byte[] rowbprev = null; // rowb prev
- public PngWriter(OutputStream outputStream, ImageInfo imgInfo) {
+ // this only influences the 1-2-4 bitdepth format - and if we pass a ImageLine to writeRow, this is ignored
+ private boolean unpackedMode = false;
+
+ public PngWriter(final OutputStream outputStream, final ImageInfo imgInfo) {
this(outputStream, imgInfo, "[NO FILENAME AVAILABLE]");
}
/**
- * Constructs a new PngWriter from a output stream.
+ * Constructs a new PngWriter from a output stream. After construction
+ * nothing is writen yet. You still can set some parameters (compression,
+ * filters) and queue chunks before start writing the pixels.
* <p>
* See also <code>FileHelper.createPngWriter()</code> if available.
- *
+ *
* @param outputStream
* Opened stream for binary writing
* @param imgInfo
@@ -62,179 +96,164 @@ public class PngWriter {
* @param filenameOrDescription
* Optional, just for error/debug messages
*/
- public PngWriter(OutputStream outputStream, ImageInfo imgInfo, String filenameOrDescription) {
+ public PngWriter(final OutputStream outputStream, final ImageInfo imgInfo, final String filenameOrDescription) {
this.filename = filenameOrDescription == null ? "" : filenameOrDescription;
this.os = outputStream;
this.imgInfo = imgInfo;
// prealloc
- scanline = new int[imgInfo.samplesPerRowP];
rowb = new byte[imgInfo.bytesPerRow + 1];
rowbprev = new byte[rowb.length];
rowbfilter = new byte[rowb.length];
- datStream = new PngIDatChunkOutputStream(this.os);
- chunkList = new ChunkList(imgInfo);
- metadata = new PngMetadata(chunkList, false);
- filterStrat = new FilterWriteStrategy(imgInfo, FilterType.FILTER_DEFAULT);
+ chunksList = new ChunksListForWrite(imgInfo);
+ metadata = new PngMetadata(chunksList);
+ filterStrat = new FilterWriteStrategy(imgInfo, FilterType.FILTER_DEFAULT); // can be changed
}
- /**
- * Write id signature and also "IHDR" chunk
- */
- private void writeSignatureAndIHDR() {
- currentChunkGroup = ChunkList.CHUNK_GROUP_0_IDHR;
- if (datStreamDeflated == null) {
- Deflater def = new Deflater(compLevel);
- def.setStrategy(deflaterStrategy);
- datStreamDeflated = new DeflaterOutputStream(datStream, def, 8192);
+ private void init() {
+ datStream = new PngIDatChunkOutputStream(this.os, idatMaxSize);
+ final Deflater def = new Deflater(compLevel);
+ def.setStrategy(deflaterStrategy);
+ datStreamDeflated = new DeflaterOutputStream(datStream, def);
+ writeSignatureAndIHDR();
+ writeFirstChunks();
+ }
+
+ private void reportResultsForFilter(final int rown, final FilterType type, final boolean tentative) {
+ Arrays.fill(histox, 0);
+ int s = 0, v;
+ for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
+ v = rowbfilter[i];
+ if (v < 0)
+ s -= v;
+ else
+ s += v;
+ histox[v & 0xFF]++;
}
- PngHelper.writeBytes(os, PngHelper.pngIdBytes); // signature
- PngChunkIHDR ihdr = new PngChunkIHDR(imgInfo);
- // http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
- ihdr.setCols(imgInfo.cols);
- ihdr.setRows(imgInfo.rows);
- ihdr.setBitspc(imgInfo.bitDepth);
- int colormodel = 0;
- if (imgInfo.alpha)
- colormodel += 0x04;
- if (imgInfo.indexed)
- colormodel += 0x01;
- if (!imgInfo.greyscale)
- colormodel += 0x02;
- ihdr.setColormodel(colormodel);
- ihdr.setCompmeth(0); // compression method 0=deflate
- ihdr.setFilmeth(0); // filter method (0)
- ihdr.setInterlaced(0); // we never interlace
- ihdr.createChunk().writeChunk(os);
+ filterStrat.fillResultsForFilter(rown, type, s, histox, tentative);
+ }
+ private void writeEndChunk() {
+ final PngChunkIEND c = new PngChunkIEND(imgInfo);
+ c.createRawChunk().writeChunk(os);
}
private void writeFirstChunks() {
int nw = 0;
- currentChunkGroup = ChunkList.CHUNK_GROUP_1_AFTERIDHR;
- nw = chunkList.writeChunks(os, currentChunkGroup);
- currentChunkGroup = ChunkList.CHUNK_GROUP_2_PLTE;
- nw = chunkList.writeChunks(os, currentChunkGroup);
+ currentChunkGroup = ChunksList.CHUNK_GROUP_1_AFTERIDHR;
+ nw = chunksList.writeChunks(os, currentChunkGroup);
+ currentChunkGroup = ChunksList.CHUNK_GROUP_2_PLTE;
+ nw = chunksList.writeChunks(os, currentChunkGroup);
if (nw > 0 && imgInfo.greyscale)
throw new PngjOutputException("cannot write palette for this format");
if (nw == 0 && imgInfo.indexed)
throw new PngjOutputException("missing palette");
- currentChunkGroup = ChunkList.CHUNK_GROUP_3_AFTERPLTE;
- nw = chunkList.writeChunks(os, currentChunkGroup);
- currentChunkGroup = ChunkList.CHUNK_GROUP_4_IDAT;
+ currentChunkGroup = ChunksList.CHUNK_GROUP_3_AFTERPLTE;
+ nw = chunksList.writeChunks(os, currentChunkGroup);
+ currentChunkGroup = ChunksList.CHUNK_GROUP_4_IDAT;
}
private void writeLastChunks() { // not including end
- currentChunkGroup = ChunkList.CHUNK_GROUP_5_AFTERIDAT;
- chunkList.writeChunks(os, currentChunkGroup);
+ currentChunkGroup = ChunksList.CHUNK_GROUP_5_AFTERIDAT;
+ chunksList.writeChunks(os, currentChunkGroup);
// should not be unwriten chunks
- List<PngChunk> pending = chunkList.getQueuedChunks();
+ final List<PngChunk> pending = chunksList.getQueuedChunks();
if (!pending.isEmpty())
throw new PngjOutputException(pending.size() + " chunks were not written! Eg: " + pending.get(0).toString());
- currentChunkGroup = ChunkList.CHUNK_GROUP_6_END;
- }
-
- private void writeEndChunk() {
- PngChunkIEND c = new PngChunkIEND(imgInfo);
- c.createChunk().writeChunk(os);
+ currentChunkGroup = ChunksList.CHUNK_GROUP_6_END;
}
/**
- * Writes a full image row. This must be called sequentially from n=0 to n=rows-1 One integer per sample , in the
- * natural order: R G B R G B ... (or R G B A R G B A... if has alpha) The values should be between 0 and 255 for 8
- * bitspc images, and between 0- 65535 form 16 bitspc images (this applies also to the alpha channel if present) The
- * array can be reused.
- *
- * @param newrow
- * Array of pixel values
- * @param rown
- * Row number, from 0 (top) to rows-1 (bottom). This is just used as a check. Pass -1 if you want to
- * autocompute it
+ * Write id signature and also "IHDR" chunk
*/
- public void writeRow(int[] newrow, int rown) {
- if (rown == 0) {
- writeSignatureAndIHDR();
- writeFirstChunks();
- }
- if (rown < -1 || rown > imgInfo.rows)
- throw new RuntimeException("invalid value for row " + rown);
- rowNum++;
- if (rown >= 0 && rowNum != rown)
- throw new RuntimeException("rows must be written in strict consecutive order: tried to write row " + rown
- + ", expected=" + rowNum);
- scanline = newrow;
- // swap
- byte[] tmp = rowb;
- rowb = rowbprev;
- rowbprev = tmp;
- convertRowToBytes();
- filterRow(rown);
- try {
- datStreamDeflated.write(rowbfilter, 0, imgInfo.bytesPerRow + 1);
- } catch (IOException e) {
- throw new PngjOutputException(e);
- }
- }
+ private void writeSignatureAndIHDR() {
+ currentChunkGroup = ChunksList.CHUNK_GROUP_0_IDHR;
- /**
- * Same as writeRow(int[] newrow, int rown), but does not check row number
- *
- * @param newrow
- */
- public void writeRow(int[] newrow) {
- writeRow(newrow, -1);
- }
+ PngHelperInternal.writeBytes(os, PngHelperInternal.getPngIdSignature()); // signature
+ final PngChunkIHDR ihdr = new PngChunkIHDR(imgInfo);
+ // http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
+ ihdr.setCols(imgInfo.cols);
+ ihdr.setRows(imgInfo.rows);
+ ihdr.setBitspc(imgInfo.bitDepth);
+ int colormodel = 0;
+ if (imgInfo.alpha)
+ colormodel += 0x04;
+ if (imgInfo.indexed)
+ colormodel += 0x01;
+ if (!imgInfo.greyscale)
+ colormodel += 0x02;
+ ihdr.setColormodel(colormodel);
+ ihdr.setCompmeth(0); // compression method 0=deflate
+ ihdr.setFilmeth(0); // filter method (0)
+ ihdr.setInterlaced(0); // we never interlace
+ ihdr.createRawChunk().writeChunk(os);
- /**
- * Writes line. See writeRow(int[] newrow, int rown)
- */
- public void writeRow(ImageLine imgline, int rownumber) {
- writeRow(imgline.scanline, rownumber);
}
- /**
- * Writes line, checks that the row number is consistent with that of the ImageLine See writeRow(int[] newrow, int
- * rown)
- *
- * @deprecated Better use writeRow(ImageLine imgline, int rownumber)
- */
- public void writeRow(ImageLine imgline) {
- writeRow(imgline.scanline, imgline.getRown());
- }
+ protected void encodeRowFromByte(final byte[] row) {
+ if (row.length == imgInfo.samplesPerRowPacked) {
+ // some duplication of code - because this case is typical and it works faster this way
+ int j = 1;
+ if (imgInfo.bitDepth <= 8) {
+ for (final byte x : row) { // optimized
+ rowb[j++] = x;
+ }
+ } else { // 16 bitspc
+ for (final byte x : row) { // optimized
+ rowb[j] = x;
+ j += 2;
+ }
+ }
+ } else {
+ // perhaps we need to pack?
+ if (row.length >= imgInfo.samplesPerRow && unpackedMode)
+ ImageLine.packInplaceByte(imgInfo, row, row, false); // row is packed in place!
+ if (imgInfo.bitDepth <= 8) {
+ for (int i = 0, j = 1; i < imgInfo.samplesPerRowPacked; i++) {
+ rowb[j++] = row[i];
+ }
+ } else { // 16 bitspc
+ for (int i = 0, j = 1; i < imgInfo.samplesPerRowPacked; i++) {
+ rowb[j++] = row[i];
+ rowb[j++] = 0;
+ }
+ }
- /**
- * Finalizes the image creation and closes the stream. This MUST be called after writing the lines.
- */
- public void end() {
- if (rowNum != imgInfo.rows - 1)
- throw new PngjOutputException("all rows have not been written");
- try {
- datStreamDeflated.finish();
- datStream.flush();
- writeLastChunks();
- writeEndChunk();
- os.close();
- } catch (IOException e) {
- throw new PngjOutputException(e);
}
}
- private int[] histox = new int[256]; // auxiliar buffer, only used by reportResultsForFilter
-
- private void reportResultsForFilter(int rown, FilterType type, boolean tentative) {
- Arrays.fill(histox, 0);
- int s = 0, v;
- for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
- v = rowbfilter[i];
- if (v < 0)
- s -= (int) v;
- else
- s += (int) v;
- histox[v & 0xFF]++;
+ protected void encodeRowFromInt(final int[] row) {
+ // http://www.libpng.org/pub/png/spec/1.2/PNG-DataRep.html
+ if (row.length == imgInfo.samplesPerRowPacked) {
+ // some duplication of code - because this case is typical and it works faster this way
+ int j = 1;
+ if (imgInfo.bitDepth <= 8) {
+ for (final int x : row) { // optimized
+ rowb[j++] = (byte) x;
+ }
+ } else { // 16 bitspc
+ for (final int x : row) { // optimized
+ rowb[j++] = (byte) (x >> 8);
+ rowb[j++] = (byte) (x);
+ }
+ }
+ } else {
+ // perhaps we need to pack?
+ if (row.length >= imgInfo.samplesPerRow && unpackedMode)
+ ImageLine.packInplaceInt(imgInfo, row, row, false); // row is packed in place!
+ if (imgInfo.bitDepth <= 8) {
+ for (int i = 0, j = 1; i < imgInfo.samplesPerRowPacked; i++) {
+ rowb[j++] = (byte) (row[i]);
+ }
+ } else { // 16 bitspc
+ for (int i = 0, j = 1; i < imgInfo.samplesPerRowPacked; i++) {
+ rowb[j++] = (byte) (row[i] >> 8);
+ rowb[j++] = (byte) (row[i]);
+ }
+ }
}
- filterStrat.fillResultsForFilter(rown, type, s, histox, tentative);
}
- private void filterRow(int rown) {
+ private void filterRow(final int rown) {
// warning: filters operation rely on: "previos row" (rowbprev) is
// initialized to 0 the first time
if (filterStrat.shouldTestAll(rown)) {
@@ -249,7 +268,7 @@ public class PngWriter {
filterRowPaeth();
reportResultsForFilter(rown, FilterType.FILTER_PAETH, true);
}
- FilterType filterType = filterStrat.gimmeFilterType(rown, true);
+ final FilterType filterType = filterStrat.gimmeFilterType(rown, true);
rowbfilter[0] = (byte) filterType.val;
switch (filterType) {
case FILTER_NONE:
@@ -268,123 +287,98 @@ public class PngWriter {
filterRowPaeth();
break;
default:
- throw new PngjOutputException("Filter type " + filterType + " not implemented");
+ throw new PngjUnsupportedException("Filter type " + filterType + " not implemented");
}
reportResultsForFilter(rown, filterType, false);
}
- protected int sumRowbfilter() { // sums absolute value
- int s = 0;
- for (int i = 1; i <= imgInfo.bytesPerRow; i++)
- if (rowbfilter[i] < 0)
- s -= (int) rowbfilter[i];
- else
- s += (int) rowbfilter[i];
- return s;
+ private void prepareEncodeRow(final int rown) {
+ if (datStream == null)
+ init();
+ rowNum++;
+ if (rown >= 0 && rowNum != rown)
+ throw new PngjOutputException("rows must be written in order: expected:" + rowNum + " passed:" + rown);
+ // swap
+ final byte[] tmp = rowb;
+ rowb = rowbprev;
+ rowbprev = tmp;
}
- protected void filterRowNone() {
- for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
- rowbfilter[i] = (byte) rowb[i];
+ private void filterAndSend(final int rown) {
+ filterRow(rown);
+ try {
+ datStreamDeflated.write(rowbfilter, 0, imgInfo.bytesPerRow + 1);
+ } catch (final IOException e) {
+ throw new PngjOutputException(e);
}
}
- protected void filterRowSub() {
- int i, j;
- for (i = 1; i <= imgInfo.bytesPixel; i++)
- rowbfilter[i] = (byte) rowb[i];
- for (j = 1, i = imgInfo.bytesPixel + 1; i <= imgInfo.bytesPerRow; i++, j++) {
- rowbfilter[i] = (byte) (rowb[i] - rowb[j]);
+ protected void filterRowAverage() {
+ int i, j, imax;
+ imax = imgInfo.bytesPerRow;
+ for (j = 1 - imgInfo.bytesPixel, i = 1; i <= imax; i++, j++) {
+ rowbfilter[i] = (byte) (rowb[i] - ((rowbprev[i] & 0xFF) + (j > 0 ? (rowb[j] & 0xFF) : 0)) / 2);
}
}
- protected void filterRowUp() {
+ protected void filterRowNone() {
for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
- rowbfilter[i] = (byte) (rowb[i] - rowbprev[i]);
- }
- }
-
- protected void filterRowAverage() {
- int i, j;
- for (j = 1 - imgInfo.bytesPixel, i = 1; i <= imgInfo.bytesPerRow; i++, j++) {
- rowbfilter[i] = (byte) (rowb[i] - ((rowbprev[i] & 0xFF) + (j > 0 ? (rowb[j] & 0xFF) : 0)) / 2);
+ rowbfilter[i] = rowb[i];
}
}
protected void filterRowPaeth() {
- int i, j;
- for (j = 1 - imgInfo.bytesPixel, i = 1; i <= imgInfo.bytesPerRow; i++, j++) {
- rowbfilter[i] = (byte) (rowb[i] - FilterType.filterPaethPredictor(j > 0 ? (rowb[j] & 0xFF) : 0,
- rowbprev[i] & 0xFF, j > 0 ? (rowbprev[j] & 0xFF) : 0));
+ int i, j, imax;
+ imax = imgInfo.bytesPerRow;
+ for (j = 1 - imgInfo.bytesPixel, i = 1; i <= imax; i++, j++) {
+ // rowbfilter[i] = (byte) (rowb[i] - PngHelperInternal.filterPaethPredictor(j > 0 ? (rowb[j] & 0xFF) : 0,
+ // rowbprev[i] & 0xFF, j > 0 ? (rowbprev[j] & 0xFF) : 0));
+ rowbfilter[i] = (byte) PngHelperInternal.filterRowPaeth(rowb[i], j > 0 ? (rowb[j] & 0xFF) : 0,
+ rowbprev[i] & 0xFF, j > 0 ? (rowbprev[j] & 0xFF) : 0);
}
}
- protected void convertRowToBytes() {
- // http://www.libpng.org/pub/png/spec/1.2/PNG-DataRep.html
+ protected void filterRowSub() {
int i, j;
- if (imgInfo.bitDepth <= 8) {
- for (i = 0, j = 1; i < imgInfo.samplesPerRowP; i++) {
- rowb[j++] = (byte) (scanline[i]);
- }
- } else { // 16 bitspc
- for (i = 0, j = 1; i < imgInfo.samplesPerRowP; i++) {
- // x = (int) (scanline[i]) & 0xFFFF;
- rowb[j++] = (byte) (scanline[i] >> 8);
- rowb[j++] = (byte) (scanline[i]);
- }
+ for (i = 1; i <= imgInfo.bytesPixel; i++)
+ rowbfilter[i] = rowb[i];
+ for (j = 1, i = imgInfo.bytesPixel + 1; i <= imgInfo.bytesPerRow; i++, j++) {
+ // !!! rowbfilter[i] = (byte) (rowb[i] - rowb[j]);
+ rowbfilter[i] = (byte) PngHelperInternal.filterRowSub(rowb[i], rowb[j]);
}
}
- // /// several getters / setters - all this setters are optional
-
- /**
- * Filename or description, from the optional constructor argument.
- */
- public String getFilename() {
- return filename;
+ protected void filterRowUp() {
+ for (int i = 1; i <= imgInfo.bytesPerRow; i++) {
+ // rowbfilter[i] = (byte) (rowb[i] - rowbprev[i]); !!!
+ rowbfilter[i] = (byte) PngHelperInternal.filterRowUp(rowb[i], rowbprev[i]);
+ }
}
- /**
- * Sets internal prediction filter type, or strategy to choose it.
- * <p>
- * This must be called just after constructor, before starting writing.
- * <p>
- * See also setCompLevel()
- *
- * @param filterType
- * One of the five prediction types or strategy to choose it (see <code>PngFilterType</code>) Recommended
- * values: DEFAULT (default) or AGGRESIVE
- */
- public void setFilterType(FilterType filterType) {
- filterStrat = new FilterWriteStrategy(imgInfo, filterType);
+ protected int sumRowbfilter() { // sums absolute value
+ int s = 0;
+ for (int i = 1; i <= imgInfo.bytesPerRow; i++)
+ if (rowbfilter[i] < 0)
+ s -= rowbfilter[i];
+ else
+ s += rowbfilter[i];
+ return s;
}
/**
- * Sets compression level of ZIP algorithm.
+ * copy chunks from reader - copy_mask : see ChunksToWrite.COPY_XXX
* <p>
- * This must be called just after constructor, before starting writing.
+ * If we are after idat, only considers those chunks after IDAT in PngReader
* <p>
- * See also setFilterType()
- *
- * @param compLevel
- * between 0 and 9 (default:6 , recommended: 6 or more)
+ * TODO: this should be more customizable
*/
- public void setCompLevel(int compLevel) {
- if (compLevel < 0 || compLevel > 9)
- throw new PngjException("Compression level invalid (" + compLevel + ") Must be 0..9");
- this.compLevel = compLevel;
- }
-
- /**
- * copy chunks from reader - copy_mask : see ChunksToWrite.COPY_XXX
- *
- * If we are after idat, only considers those chunks after IDAT in PngReader TODO: this should be more customizable
- */
- private void copyChunks(PngReader reader, int copy_mask, boolean onlyAfterIdat) {
- boolean idatDone = currentChunkGroup >= ChunkList.CHUNK_GROUP_4_IDAT;
- for (PngChunk chunk : reader.getChunksList().getChunks()) {
- int group = chunk.getChunkGroup();
- if (group < ChunkList.CHUNK_GROUP_4_IDAT && idatDone)
+ private void copyChunks(final PngReader reader, final int copy_mask, final boolean onlyAfterIdat) {
+ final boolean idatDone = currentChunkGroup >= ChunksList.CHUNK_GROUP_4_IDAT;
+ if (onlyAfterIdat && reader.getCurrentChunkGroup() < ChunksList.CHUNK_GROUP_6_END)
+ throw new PngjExceptionInternal("tried to copy last chunks but reader has not ended");
+ for (final PngChunk chunk : reader.getChunksList().getChunks()) {
+ final int group = chunk.getChunkGroup();
+ if (group < ChunksList.CHUNK_GROUP_4_IDAT && idatDone)
continue;
boolean copy = false;
if (chunk.crit) {
@@ -395,8 +389,8 @@ public class PngWriter {
copy = true;
}
} else { // ancillary
- boolean text = (chunk instanceof PngChunkTextVar);
- boolean safe = chunk.safe;
+ final boolean text = (chunk instanceof PngChunkTextVar);
+ final boolean safe = chunk.safe;
// notice that these if are not exclusive
if (ChunkHelper.maskMatch(copy_mask, ChunkCopyBehaviour.COPY_ALL))
copy = true;
@@ -413,9 +407,11 @@ public class PngWriter {
&& !(ChunkHelper.isUnknown(chunk) || text || chunk.id.equals(ChunkHelper.hIST) || chunk.id
.equals(ChunkHelper.tIME)))
copy = true;
+ if (chunk instanceof PngChunkSkipped)
+ copy = false;
}
if (copy) {
- chunkList.queueChunk(PngChunk.cloneChunk(chunk, imgInfo), !chunk.allowsMultiple(), false);
+ chunksList.queue(PngChunk.cloneChunk(chunk, imgInfo));
}
}
}
@@ -423,40 +419,271 @@ public class PngWriter {
/**
* Copies first (pre IDAT) ancillary chunks from a PngReader.
* <p>
- * Should be called when creating an image from another, before starting writing lines, to copy relevant chunks.
+ * Should be called when creating an image from another, before starting
+ * writing lines, to copy relevant chunks.
* <p>
- *
+ *
* @param reader
* : PngReader object, already opened.
* @param copy_mask
- * : Mask bit (OR), see <code>ChunksToWrite.COPY_XXX</code> constants
+ * : Mask bit (OR), see <code>ChunksToWrite.COPY_XXX</code>
+ * constants
*/
- public void copyChunksFirst(PngReader reader, int copy_mask) {
+ public void copyChunksFirst(final PngReader reader, final int copy_mask) {
copyChunks(reader, copy_mask, false);
}
/**
* Copies last (post IDAT) ancillary chunks from a PngReader.
* <p>
- * Should be called when creating an image from another, after writing all lines, before closing the writer, to copy
- * additional chunks.
+ * Should be called when creating an image from another, after writing all
+ * lines, before closing the writer, to copy additional chunks.
* <p>
- *
+ *
* @param reader
* : PngReader object, already opened and fully read.
* @param copy_mask
- * : Mask bit (OR), see <code>ChunksToWrite.COPY_XXX</code> constants
+ * : Mask bit (OR), see <code>ChunksToWrite.COPY_XXX</code>
+ * constants
*/
- public void copyChunksLast(PngReader reader, int copy_mask) {
+ public void copyChunksLast(final PngReader reader, final int copy_mask) {
copyChunks(reader, copy_mask, true);
}
- public ChunkList getChunkList() {
- return chunkList;
+ /**
+ * Computes compressed size/raw size, approximate.
+ * <p>
+ * Actually: compressed size = total size of IDAT data , raw size =
+ * uncompressed pixel bytes = rows * (bytesPerRow + 1).
+ *
+ * This must be called after pngw.end()
+ */
+ public double computeCompressionRatio() {
+ if (currentChunkGroup < ChunksList.CHUNK_GROUP_6_END)
+ throw new PngjOutputException("must be called after end()");
+ final double compressed = datStream.getCountFlushed();
+ final double raw = (imgInfo.bytesPerRow + 1) * imgInfo.rows;
+ return compressed / raw;
+ }
+
+ /**
+ * Finalizes the image creation and closes the stream. This MUST be called
+ * after writing the lines.
+ */
+ public void end() {
+ if (rowNum != imgInfo.rows - 1)
+ throw new PngjOutputException("all rows have not been written");
+ try {
+ datStreamDeflated.finish();
+ datStream.flush();
+ writeLastChunks();
+ writeEndChunk();
+ if (shouldCloseStream)
+ os.close();
+ } catch (final IOException e) {
+ throw new PngjOutputException(e);
+ }
+ }
+
+ /**
+ * returns the chunks list (queued and writen chunks)
+ */
+ public ChunksListForWrite getChunksList() {
+ return chunksList;
}
+ /**
+ * Filename or description, from the optional constructor argument.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * High level wrapper over chunksList for metadata handling
+ */
public PngMetadata getMetadata() {
return metadata;
}
+ /**
+ * Sets compression level of ZIP algorithm.
+ * <p>
+ * This must be called just after constructor, before starting writing.
+ * <p>
+ * See also setFilterType()
+ *
+ * @param compLevel
+ * between 0 and 9 (default:6 , recommended: 6 or more)
+ */
+ public void setCompLevel(final int compLevel) {
+ if (compLevel < 0 || compLevel > 9)
+ throw new PngjOutputException("Compression level invalid (" + compLevel + ") Must be 0..9");
+ this.compLevel = compLevel;
+ }
+
+ /**
+ * Sets internal prediction filter type, or strategy to choose it.
+ * <p>
+ * This must be called just after constructor, before starting writing.
+ * <p>
+ * See also setCompLevel()
+ *
+ * @param filterType
+ * One of the five prediction types or strategy to choose it (see
+ * <code>PngFilterType</code>) Recommended values: DEFAULT
+ * (default) or AGGRESIVE
+ */
+ public void setFilterType(final FilterType filterType) {
+ filterStrat = new FilterWriteStrategy(imgInfo, filterType);
+ }
+
+ /**
+ * Sets maximum size of IDAT fragments. This has little effect on
+ * performance you should rarely call this
+ * <p>
+ *
+ * @param idatMaxSize
+ * default=0 : use defaultSize (32K)
+ */
+ public void setIdatMaxSize(final int idatMaxSize) {
+ this.idatMaxSize = idatMaxSize;
+ }
+
+ /**
+ * if true, input stream will be closed after ending write
+ * <p>
+ * default=true
+ */
+ public void setShouldCloseStream(final boolean shouldCloseStream) {
+ this.shouldCloseStream = shouldCloseStream;
+ }
+
+ /**
+ * Deflater strategy: one of Deflater.FILTERED Deflater.HUFFMAN_ONLY
+ * Deflater.DEFAULT_STRATEGY
+ * <p>
+ * Default: Deflater.FILTERED . This should be changed very rarely.
+ */
+ public void setDeflaterStrategy(final int deflaterStrategy) {
+ this.deflaterStrategy = deflaterStrategy;
+ }
+
+ /**
+ * Writes line, checks that the row number is consistent with that of the
+ * ImageLine See writeRow(int[] newrow, int rown)
+ *
+ * @deprecated Better use writeRow(ImageLine imgline, int rownumber)
+ */
+ public void writeRow(final ImageLine imgline) {
+ writeRow(imgline.scanline, imgline.getRown());
+ }
+
+ /**
+ * Writes line. See writeRow(int[] newrow, int rown)
+ *
+ * The <tt>packed</tt> flag of the imageline is honoured!
+ *
+ * @see #writeRowInt(int[], int)
+ */
+ public void writeRow(final ImageLine imgline, final int rownumber) {
+ unpackedMode = imgline.samplesUnpacked;
+ if (imgline.sampleType == SampleType.INT)
+ writeRowInt(imgline.scanline, rownumber);
+ else
+ writeRowByte(imgline.scanlineb, rownumber);
+ }
+
+ /**
+ * Same as writeRow(int[] newrow, int rown), but does not check row number
+ *
+ * @param newrow
+ */
+ public void writeRow(final int[] newrow) {
+ writeRow(newrow, -1);
+ }
+
+ /**
+ * Alias to writeRowInt
+ *
+ * @see #writeRowInt(int[], int)
+ */
+ public void writeRow(final int[] newrow, final int rown) {
+ writeRowInt(newrow, rown);
+ }
+
+ /**
+ * Writes a full image row.
+ * <p>
+ * This must be called sequentially from n=0 to n=rows-1 One integer per
+ * sample , in the natural order: R G B R G B ... (or R G B A R G B A... if
+ * has alpha) The values should be between 0 and 255 for 8 bitspc images,
+ * and between 0- 65535 form 16 bitspc images (this applies also to the
+ * alpha channel if present) The array can be reused.
+ * <p>
+ * Warning: the array might be modified in some cases (unpacked row with low
+ * bitdepth)
+ * <p>
+ *
+ * @param newrow
+ * Array of pixel values. Warning: the array size should be exact
+ * (samplesPerRowP)
+ * @param rown
+ * Row number, from 0 (top) to rows-1 (bottom). This is just used
+ * as a check. Pass -1 if you want to autocompute it
+ */
+ public void writeRowInt(final int[] newrow, final int rown) {
+ prepareEncodeRow(rown);
+ encodeRowFromInt(newrow);
+ filterAndSend(rown);
+ }
+
+ /**
+ * Same semantics as writeRowInt but using bytes. Each byte is still a
+ * sample. If 16bitdepth, we are passing only the most significant byte (and
+ * hence losing some info)
+ *
+ * @see PngWriter#writeRowInt(int[], int)
+ */
+ public void writeRowByte(final byte[] newrow, final int rown) {
+ prepareEncodeRow(rown);
+ encodeRowFromByte(newrow);
+ filterAndSend(rown);
+ }
+
+ /**
+ * Writes all the pixels, calling writeRowInt() for each image row
+ */
+ public void writeRowsInt(final int[][] image) {
+ for (int i = 0; i < imgInfo.rows; i++)
+ writeRowInt(image[i], i);
+ }
+
+ /**
+ * Writes all the pixels, calling writeRowByte() for each image row
+ */
+ public void writeRowsByte(final byte[][] image) {
+ for (int i = 0; i < imgInfo.rows; i++)
+ writeRowByte(image[i], i);
+ }
+
+ public boolean isUnpackedMode() {
+ return unpackedMode;
+ }
+
+ /**
+ * If false (default), and image has bitdepth 1-2-4, the scanlines passed
+ * are assumed to be already packed.
+ * <p>
+ * If true, each element is a sample, the writer will perform the packing if
+ * necessary.
+ * <p>
+ * Warning: when using {@link #writeRow(ImageLine, int)} (recommended) the
+ * <tt>packed</tt> flag of the ImageLine object overrides (and overwrites!)
+ * this field.
+ */
+ public void setUseUnPackedMode(final boolean useUnpackedMode) {
+ this.unpackedMode = useUnpackedMode;
+ }
+
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjBadCrcException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjBadCrcException.java
index 3b74f862f..032b2ed3a 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngjBadCrcException.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjBadCrcException.java
@@ -6,15 +6,15 @@ package jogamp.opengl.util.pngj;
public class PngjBadCrcException extends PngjInputException {
private static final long serialVersionUID = 1L;
- public PngjBadCrcException(String message, Throwable cause) {
+ public PngjBadCrcException(final String message, final Throwable cause) {
super(message, cause);
}
- public PngjBadCrcException(String message) {
+ public PngjBadCrcException(final String message) {
super(message);
}
- public PngjBadCrcException(Throwable cause) {
+ public PngjBadCrcException(final Throwable cause) {
super(cause);
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java
index 4a45cb5bf..3d05589b1 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java
@@ -2,22 +2,22 @@ package jogamp.opengl.util.pngj;
/**
* Generic exception
- *
+ *
* @author Hernan J Gonzalez
- *
+ *
*/
public class PngjException extends RuntimeException {
private static final long serialVersionUID = 1L;
- public PngjException(String message, Throwable cause) {
+ public PngjException(final String message, final Throwable cause) {
super(message, cause);
}
- public PngjException(String message) {
+ public PngjException(final String message) {
super(message);
}
- public PngjException(Throwable cause) {
+ public PngjException(final Throwable cause) {
super(cause);
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjExceptionInternal.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjExceptionInternal.java
new file mode 100644
index 000000000..9484abf5e
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjExceptionInternal.java
@@ -0,0 +1,24 @@
+package jogamp.opengl.util.pngj;
+
+/**
+ * Exception for anomalous internal problems (sort of asserts) that point to
+ * some issue with the library
+ *
+ * @author Hernan J Gonzalez
+ *
+ */
+public class PngjExceptionInternal extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public PngjExceptionInternal(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public PngjExceptionInternal(final String message) {
+ super(message);
+ }
+
+ public PngjExceptionInternal(final Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjInputException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjInputException.java
index 5cc36b99a..c92d80b2c 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngjInputException.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjInputException.java
@@ -6,15 +6,15 @@ package jogamp.opengl.util.pngj;
public class PngjInputException extends PngjException {
private static final long serialVersionUID = 1L;
- public PngjInputException(String message, Throwable cause) {
+ public PngjInputException(final String message, final Throwable cause) {
super(message, cause);
}
- public PngjInputException(String message) {
+ public PngjInputException(final String message) {
super(message);
}
- public PngjInputException(Throwable cause) {
+ public PngjInputException(final Throwable cause) {
super(cause);
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjOutputException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjOutputException.java
index c8cd36acb..4e9cdc950 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngjOutputException.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjOutputException.java
@@ -6,15 +6,15 @@ package jogamp.opengl.util.pngj;
public class PngjOutputException extends PngjException {
private static final long serialVersionUID = 1L;
- public PngjOutputException(String message, Throwable cause) {
+ public PngjOutputException(final String message, final Throwable cause) {
super(message, cause);
}
- public PngjOutputException(String message) {
+ public PngjOutputException(final String message) {
super(message);
}
- public PngjOutputException(Throwable cause) {
+ public PngjOutputException(final Throwable cause) {
super(cause);
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjUnsupportedException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjUnsupportedException.java
index 0801e33bb..e68b153ac 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngjUnsupportedException.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjUnsupportedException.java
@@ -1,7 +1,8 @@
package jogamp.opengl.util.pngj;
/**
- * Exception thrown because of some valid feature of PNG standard that this library does not support
+ * Exception thrown because of some valid feature of PNG standard that this
+ * library does not support
*/
public class PngjUnsupportedException extends RuntimeException {
private static final long serialVersionUID = 1L;
@@ -10,15 +11,15 @@ public class PngjUnsupportedException extends RuntimeException {
super();
}
- public PngjUnsupportedException(String message, Throwable cause) {
+ public PngjUnsupportedException(final String message, final Throwable cause) {
super(message, cause);
}
- public PngjUnsupportedException(String message) {
+ public PngjUnsupportedException(final String message) {
super(message);
}
- public PngjUnsupportedException(Throwable cause) {
+ public PngjUnsupportedException(final Throwable cause) {
super(cause);
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java b/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java
index bbec247fb..248472298 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java
@@ -4,12 +4,14 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
- * stream that outputs to memory and allows to flush fragments every 'size' bytes to some other destination
+ * stream that outputs to memory and allows to flush fragments every 'size'
+ * bytes to some other destination
*/
abstract class ProgressiveOutputStream extends ByteArrayOutputStream {
private final int size;
+ private long countFlushed = 0;
- public ProgressiveOutputStream(int size) {
+ public ProgressiveOutputStream(final int size) {
this.size = size;
}
@@ -26,19 +28,19 @@ abstract class ProgressiveOutputStream extends ByteArrayOutputStream {
}
@Override
- public final void write(byte[] b, int off, int len) {
+ public final void write(final byte[] b, final int off, final int len) {
super.write(b, off, len);
checkFlushBuffer(false);
}
@Override
- public final void write(byte[] b) throws IOException {
+ public final void write(final byte[] b) throws IOException {
super.write(b);
checkFlushBuffer(false);
}
@Override
- public final void write(int arg0) {
+ public final void write(final int arg0) {
super.write(arg0);
checkFlushBuffer(false);
}
@@ -49,10 +51,10 @@ abstract class ProgressiveOutputStream extends ByteArrayOutputStream {
}
/**
- * if it's time to flush data (or if forced==true) calls abstract method flushBuffer() and cleans those bytes from
- * own buffer
+ * if it's time to flush data (or if forced==true) calls abstract method
+ * flushBuffer() and cleans those bytes from own buffer
*/
- private final void checkFlushBuffer(boolean forced) {
+ private final void checkFlushBuffer(final boolean forced) {
while (forced || count >= size) {
int nb = size;
if (nb > count)
@@ -60,12 +62,17 @@ abstract class ProgressiveOutputStream extends ByteArrayOutputStream {
if (nb == 0)
return;
flushBuffer(buf, nb);
- int bytesleft = count - nb;
+ countFlushed += nb;
+ final int bytesleft = count - nb;
count = bytesleft;
if (bytesleft > 0)
System.arraycopy(buf, nb, buf, 0, bytesleft);
}
}
- public abstract void flushBuffer(byte[] b, int n);
+ protected abstract void flushBuffer(byte[] b, int n);
+
+ public long getCountFlushed() {
+ return countFlushed;
+ }
} \ No newline at end of file
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkCopyBehaviour.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkCopyBehaviour.java
index 43c0cb135..a2d976fac 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkCopyBehaviour.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkCopyBehaviour.java
@@ -1,8 +1,9 @@
package jogamp.opengl.util.pngj.chunks;
/**
- * Chunk copy policy to apply when copyng from a pngReader to a pngWriter http://www.w3.org/TR/PNG/#14
+ * Chunk copy policy to apply when copyng from a pngReader to a pngWriter.
* <p>
+ * http://www.w3.org/TR/PNG/#14 <br>
* These are masks, can be OR-ed
**/
public class ChunkCopyBehaviour {
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkHelper.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkHelper.java
index 26dafd4eb..b8cfd8691 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkHelper.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkHelper.java
@@ -1,134 +1,299 @@
-package jogamp.opengl.util.pngj.chunks;
-
-// see http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
-// http://www.w3.org/TR/PNG/#5Chunk-naming-conventions
-// http://www.w3.org/TR/PNG/#table53
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Set;
-import java.util.zip.DeflaterOutputStream;
-import java.util.zip.InflaterInputStream;
-
-import jogamp.opengl.util.pngj.PngHelper;
-import jogamp.opengl.util.pngj.PngjException;
-
-
-public class ChunkHelper {
- public static final String IHDR = "IHDR";
- public static final String PLTE = "PLTE";
- public static final String IDAT = "IDAT";
- public static final String IEND = "IEND";
- public static final byte[] b_IHDR = toBytes(IHDR);
- public static final byte[] b_PLTE = toBytes(PLTE);
- public static final byte[] b_IDAT = toBytes(IDAT);
- public static final byte[] b_IEND = toBytes(IEND);
-
- public static final String cHRM = "cHRM";
- public static final String gAMA = "gAMA";
- public static final String iCCP = "iCCP";
- public static final String sBIT = "sBIT";
- public static final String sRGB = "sRGB";
- public static final String bKGD = "bKGD";
- public static final String hIST = "hIST";
- public static final String tRNS = "tRNS";
- public static final String pHYs = "pHYs";
- public static final String sPLT = "sPLT";
- public static final String tIME = "tIME";
- public static final String iTXt = "iTXt";
- public static final String tEXt = "tEXt";
- public static final String zTXt = "zTXt";
-
- public static Set<String> KNOWN_CHUNKS_CRITICAL = PngHelper.asSet(IHDR, PLTE, IDAT, IEND);
-
- public static byte[] toBytes(String x) {
- return x.getBytes(PngHelper.charsetLatin1);
- }
-
- public static String toString(byte[] x) {
- return new String(x, PngHelper.charsetLatin1);
- }
-
- public static boolean isCritical(String id) { // critical chunk ?
- // first letter is uppercase
- return (Character.isUpperCase(id.charAt(0)));
- }
-
- public static boolean isPublic(String id) { // public chunk?
- // second letter is uppercase
- return (Character.isUpperCase(id.charAt(1)));
- }
-
- /**
- * "Unknown" just means that our chunk factory (even when it has been augmented by client code) did not recognize its id
- */
- public static boolean isUnknown(PngChunk c) {
- return c instanceof PngChunkUNKNOWN;
- }
-
- public static boolean isSafeToCopy(String id) { // safe to copy?
- // fourth letter is lower case
- return (!Character.isUpperCase(id.charAt(3)));
- }
-
- public static int posNullByte(byte[] b) {
- for (int i = 0; i < b.length; i++)
- if (b[i] == 0)
- return i;
- return -1;
- }
-
- public static boolean shouldLoad(String id, ChunkLoadBehaviour behav) {
- if (isCritical(id))
- return true;
- boolean kwown = PngChunk.isKnown(id);
- switch (behav) {
- case LOAD_CHUNK_ALWAYS:
- return true;
- case LOAD_CHUNK_IF_SAFE:
- return kwown || isSafeToCopy(id);
- case LOAD_CHUNK_KNOWN:
- return kwown;
- case LOAD_CHUNK_NEVER:
- return false;
- }
- return false; // should not reach here
- }
-
- public final static byte[] compressBytes(byte[] ori, boolean compress) {
- return compressBytes(ori, 0, ori.length, compress);
- }
-
- public static byte[] compressBytes(byte[] ori, int offset, int len, boolean compress) {
- try {
- ByteArrayInputStream inb = new ByteArrayInputStream(ori, offset, len);
- InputStream in = compress ? inb : new InflaterInputStream(inb);
- ByteArrayOutputStream outb = new ByteArrayOutputStream();
- OutputStream out = compress ? new DeflaterOutputStream(outb) : outb;
- shovelInToOut(in, out);
- in.close();
- out.close();
- return outb.toByteArray();
- } catch (Exception e) {
- throw new PngjException(e);
- }
- }
-
- /**
- * Shovels all data from an input stream to an output stream.
- */
- private static void shovelInToOut(InputStream in, OutputStream out) throws IOException {
- byte[] buffer = new byte[1024];
- int len;
- while ((len = in.read(buffer)) > 0) {
- out.write(buffer, 0, len);
- }
- }
-
- public static boolean maskMatch(int v, int mask) {
- return (v & mask) != 0;
- }
-
-}
+package jogamp.opengl.util.pngj.chunks;
+
+
+// see http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
+// http://www.w3.org/TR/PNG/#5Chunk-naming-conventions
+// http://www.w3.org/TR/PNG/#table53
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+import jogamp.opengl.util.pngj.PngHelperInternal;
+import jogamp.opengl.util.pngj.PngjException;
+
+
+public class ChunkHelper {
+ public static final String IHDR = "IHDR";
+ public static final String PLTE = "PLTE";
+ public static final String IDAT = "IDAT";
+ public static final String IEND = "IEND";
+ public static final byte[] b_IHDR = toBytes(IHDR);
+ public static final byte[] b_PLTE = toBytes(PLTE);
+ public static final byte[] b_IDAT = toBytes(IDAT);
+ public static final byte[] b_IEND = toBytes(IEND);
+
+ public static final String cHRM = "cHRM";
+ public static final String gAMA = "gAMA";
+ public static final String iCCP = "iCCP";
+ public static final String sBIT = "sBIT";
+ public static final String sRGB = "sRGB";
+ public static final String bKGD = "bKGD";
+ public static final String hIST = "hIST";
+ public static final String tRNS = "tRNS";
+ public static final String pHYs = "pHYs";
+ public static final String sPLT = "sPLT";
+ public static final String tIME = "tIME";
+ public static final String iTXt = "iTXt";
+ public static final String tEXt = "tEXt";
+ public static final String zTXt = "zTXt";
+
+ private static final ThreadLocal<Inflater> inflaterProvider = new ThreadLocal<Inflater>() {
+ @Override
+ protected Inflater initialValue() {
+ return new Inflater();
+ }
+ };
+
+ private static final ThreadLocal<Deflater> deflaterProvider = new ThreadLocal<Deflater>() {
+ @Override
+ protected Deflater initialValue() {
+ return new Deflater();
+ }
+ };
+
+ /*
+ * static auxiliary buffer. any method that uses this should synchronize against this
+ */
+ private static byte[] tmpbuffer = new byte[4096];
+
+ /**
+ * Converts to bytes using Latin1 (ISO-8859-1)
+ */
+ public static byte[] toBytes(final String x) {
+ return x.getBytes(PngHelperInternal.charsetLatin1);
+ }
+
+ /**
+ * Converts to String using Latin1 (ISO-8859-1)
+ */
+ public static String toString(final byte[] x) {
+ return new String(x, PngHelperInternal.charsetLatin1);
+ }
+
+ /**
+ * Converts to String using Latin1 (ISO-8859-1)
+ */
+ public static String toString(final byte[] x, final int offset, final int len) {
+ return new String(x, offset, len, PngHelperInternal.charsetLatin1);
+ }
+
+ /**
+ * Converts to bytes using UTF-8
+ */
+ public static byte[] toBytesUTF8(final String x) {
+ return x.getBytes(PngHelperInternal.charsetUTF8);
+ }
+
+ /**
+ * Converts to string using UTF-8
+ */
+ public static String toStringUTF8(final byte[] x) {
+ return new String(x, PngHelperInternal.charsetUTF8);
+ }
+
+ /**
+ * Converts to string using UTF-8
+ */
+ public static String toStringUTF8(final byte[] x, final int offset, final int len) {
+ return new String(x, offset, len, PngHelperInternal.charsetUTF8);
+ }
+
+ /**
+ * critical chunk : first letter is uppercase
+ */
+ public static boolean isCritical(final String id) {
+ return (Character.isUpperCase(id.charAt(0)));
+ }
+
+ /**
+ * public chunk: second letter is uppercase
+ */
+ public static boolean isPublic(final String id) { //
+ return (Character.isUpperCase(id.charAt(1)));
+ }
+
+ /**
+ * Safe to copy chunk: fourth letter is lower case
+ */
+ public static boolean isSafeToCopy(final String id) {
+ return (!Character.isUpperCase(id.charAt(3)));
+ }
+
+ /**
+ * "Unknown" just means that our chunk factory (even when it has been
+ * augmented by client code) did not recognize its id
+ */
+ public static boolean isUnknown(final PngChunk c) {
+ return c instanceof PngChunkUNKNOWN;
+ }
+
+ /**
+ * Finds position of null byte in array
+ *
+ * @param b
+ * @return -1 if not found
+ */
+ public static int posNullByte(final byte[] b) {
+ for (int i = 0; i < b.length; i++)
+ if (b[i] == 0)
+ return i;
+ return -1;
+ }
+
+ /**
+ * Decides if a chunk should be loaded, according to a ChunkLoadBehaviour
+ *
+ * @param id
+ * @param behav
+ * @return true/false
+ */
+ public static boolean shouldLoad(final String id, final ChunkLoadBehaviour behav) {
+ if (isCritical(id))
+ return true;
+ final boolean kwown = PngChunk.isKnown(id);
+ switch (behav) {
+ case LOAD_CHUNK_ALWAYS:
+ return true;
+ case LOAD_CHUNK_IF_SAFE:
+ return kwown || isSafeToCopy(id);
+ case LOAD_CHUNK_KNOWN:
+ return kwown;
+ case LOAD_CHUNK_NEVER:
+ return false;
+ }
+ return false; // should not reach here
+ }
+
+ public final static byte[] compressBytes(final byte[] ori, final boolean compress) {
+ return compressBytes(ori, 0, ori.length, compress);
+ }
+
+ public static byte[] compressBytes(final byte[] ori, final int offset, final int len, final boolean compress) {
+ try {
+ final ByteArrayInputStream inb = new ByteArrayInputStream(ori, offset, len);
+ final InputStream in = compress ? inb : new InflaterInputStream(inb, getInflater());
+ final ByteArrayOutputStream outb = new ByteArrayOutputStream();
+ final OutputStream out = compress ? new DeflaterOutputStream(outb) : outb;
+ shovelInToOut(in, out);
+ in.close();
+ out.close();
+ return outb.toByteArray();
+ } catch (final Exception e) {
+ throw new PngjException(e);
+ }
+ }
+
+ /**
+ * Shovels all data from an input stream to an output stream.
+ */
+ private static void shovelInToOut(final InputStream in, final OutputStream out) throws IOException {
+ synchronized (tmpbuffer) {
+ int len;
+ while ((len = in.read(tmpbuffer)) > 0) {
+ out.write(tmpbuffer, 0, len);
+ }
+ }
+ }
+
+ public static boolean maskMatch(final int v, final int mask) {
+ return (v & mask) != 0;
+ }
+
+ /**
+ * Returns only the chunks that "match" the predicate
+ *
+ * See also trimList()
+ */
+ public static List<PngChunk> filterList(final List<PngChunk> target, final ChunkPredicate predicateKeep) {
+ final List<PngChunk> result = new ArrayList<PngChunk>();
+ for (final PngChunk element : target) {
+ if (predicateKeep.match(element)) {
+ result.add(element);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Remove (in place) the chunks that "match" the predicate
+ *
+ * See also filterList
+ */
+ public static int trimList(final List<PngChunk> target, final ChunkPredicate predicateRemove) {
+ final Iterator<PngChunk> it = target.iterator();
+ int cont = 0;
+ while (it.hasNext()) {
+ final PngChunk c = it.next();
+ if (predicateRemove.match(c)) {
+ it.remove();
+ cont++;
+ }
+ }
+ return cont;
+ }
+
+ /**
+ * MY adhoc criteria: two chunks are "equivalent" ("practically equal") if
+ * they have same id and (perhaps, if multiple are allowed) if the match
+ * also in some "internal key" (eg: key for string values, palette for sPLT,
+ * etc)
+ *
+ * Notice that the use of this is optional, and that the PNG standard allows
+ * Text chunks that have same key
+ *
+ * @return true if "equivalent"
+ */
+ public static final boolean equivalent(final PngChunk c1, final PngChunk c2) {
+ if (c1 == c2)
+ return true;
+ if (c1 == null || c2 == null || !c1.id.equals(c2.id))
+ return false;
+ // same id
+ if (c1.getClass() != c2.getClass())
+ return false; // should not happen
+ if (!c2.allowsMultiple())
+ return true;
+ if (c1 instanceof PngChunkTextVar) {
+ return ((PngChunkTextVar) c1).getKey().equals(((PngChunkTextVar) c2).getKey());
+ }
+ if (c1 instanceof PngChunkSPLT) {
+ return ((PngChunkSPLT) c1).getPalName().equals(((PngChunkSPLT) c2).getPalName());
+ }
+ // unknown chunks that allow multiple? consider they don't match
+ return false;
+ }
+
+ public static boolean isText(final PngChunk c) {
+ return c instanceof PngChunkTextVar;
+ }
+
+ /**
+ * thread-local inflater, just reset : this should be only used for short
+ * individual chunks compression
+ */
+ public static Inflater getInflater() {
+ final Inflater inflater = inflaterProvider.get();
+ inflater.reset();
+ return inflater;
+ }
+
+ /**
+ * thread-local deflater, just reset : this should be only used for short
+ * individual chunks decompression
+ */
+ public static Deflater getDeflater() {
+ final Deflater deflater = deflaterProvider.get();
+ deflater.reset();
+ return deflater;
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkList.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkList.java
deleted file mode 100644
index badbbd0e8..000000000
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkList.java
+++ /dev/null
@@ -1,282 +0,0 @@
-package jogamp.opengl.util.pngj.chunks;
-
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngjException;
-
-
-/**
- * All chunks that form an image, read or to be written
- *
- * chunks include all chunks, but IDAT is a single pseudo chunk without data
- **/
-public class ChunkList {
- // ref: http://www.w3.org/TR/PNG/#table53
- public static final int CHUNK_GROUP_0_IDHR = 0; // required - single
- public static final int CHUNK_GROUP_1_AFTERIDHR = 1; // optional - multiple
- public static final int CHUNK_GROUP_2_PLTE = 2; // optional - single
- public static final int CHUNK_GROUP_3_AFTERPLTE = 3; // optional - multple
- public static final int CHUNK_GROUP_4_IDAT = 4; // required (single pseudo chunk)
- public static final int CHUNK_GROUP_5_AFTERIDAT = 5; // optional - multple
- public static final int CHUNK_GROUP_6_END = 6; // only 1 chunk - requried
-
- /**
- * All chunks, read, written (does not include IHDR, IDAT, END for written)
- */
- private List<PngChunk> chunks = new ArrayList<PngChunk>();
-
- /**
- * chunks not yet writen - does not include IHDR, IDAT, END, perhaps yes PLTE
- */
- private Set<PngChunk> queuedChunks = new LinkedHashSet<PngChunk>();
-
- final ImageInfo imageInfo; // only required for writing
-
- public ChunkList(ImageInfo imfinfo) {
- this.imageInfo = imfinfo;
- }
-
- /**
- * Adds chunk in next position. This is used when reading
- */
- public void appendReadChunk(PngChunk chunk, int chunkGroup) {
- chunk.setChunkGroup(chunkGroup);
- chunks.add(chunk);
- }
-
- public List<PngChunk> getById(String id, boolean includeQueued, boolean includeProcessed) {
- List<PngChunk> list = new ArrayList<PngChunk>();
- if (includeQueued)
- for (PngChunk c : queuedChunks)
- if (c.id.equals(id))
- list.add(c);
- if (includeProcessed)
- for (PngChunk c : chunks)
- if (c.id.equals(id))
- list.add(c);
- return list;
- }
-
- /**
- * Remove Chunk: only from queued
- */
- public boolean removeChunk(PngChunk c) {
- return queuedChunks.remove(c);
- }
-
- /**
- * add chunk to write queue
- */
- public void queueChunk(PngChunk chunk, boolean replace, boolean priority) {
- chunk.setPriority(priority);
- if (replace) {
- List<PngChunk> current = getById(chunk.id, true, false);
- for (PngChunk chunk2 : current)
- removeChunk(chunk2);
- }
- queuedChunks.add(chunk);
- }
-
- /**
- * this should be called only for ancillary chunks and PLTE (groups 1 - 3 - 5)
- **/
- private static boolean shouldWrite(PngChunk c, int currentGroup) {
- if (currentGroup == CHUNK_GROUP_2_PLTE)
- return c.id.equals(ChunkHelper.PLTE);
- if (currentGroup % 2 == 0)
- throw new RuntimeException("?");
- int minChunkGroup, maxChunkGroup;
- if (c.mustGoBeforePLTE())
- minChunkGroup = maxChunkGroup = ChunkList.CHUNK_GROUP_1_AFTERIDHR;
- else if (c.mustGoBeforeIDAT()) {
- maxChunkGroup = ChunkList.CHUNK_GROUP_3_AFTERPLTE;
- minChunkGroup = c.mustGoAfterPLTE() ? ChunkList.CHUNK_GROUP_3_AFTERPLTE : ChunkList.CHUNK_GROUP_1_AFTERIDHR;
- } else {
- maxChunkGroup = ChunkList.CHUNK_GROUP_5_AFTERIDAT;
- minChunkGroup = ChunkList.CHUNK_GROUP_1_AFTERIDHR;
- }
-
- int preferred = maxChunkGroup;
- if (c.isWritePriority())
- preferred = minChunkGroup;
- if (ChunkHelper.isUnknown(c) && c.getChunkGroup() > 0)
- preferred = c.getChunkGroup();
- if (currentGroup == preferred)
- return true;
- if (currentGroup > preferred && currentGroup <= maxChunkGroup)
- return true;
- return false;
- }
-
- public int writeChunks(OutputStream os, int currentGroup) {
- int cont = 0;
- Iterator<PngChunk> it = queuedChunks.iterator();
- while (it.hasNext()) {
- PngChunk c = it.next();
- if (!shouldWrite(c, currentGroup))
- continue;
- c.write(os);
- chunks.add(c);
- c.setChunkGroup(currentGroup);
- it.remove();
- cont++;
- }
- return cont;
- }
-
- /**
- * returns a copy of processed (read or writen) chunks
- */
- public List<PngChunk> getChunks() {
- return new ArrayList<PngChunk>(chunks);
- }
-
- public List<String> getChunksUnkown() {
- List<String> l = new ArrayList<String>();
- for (PngChunk chunk : chunks)
- if (ChunkHelper.isUnknown(chunk))
- l.add(chunk.id);
- return l;
- }
-
- /**
- * returns a copy of queued (for write) chunks
- */
- public List<PngChunk> getQueuedChunks() {
- return new ArrayList<PngChunk>(queuedChunks);
- }
-
- /**
- * behaviour:
- *
- * a chunk already processed matches : exception a chunk queued matches and overwrite=true: replace it , return true
- * a chunk queued matches and overwrite=false: do nothing, return false no matching: set it, return true
- *
- * @param c
- * @param overwriteIfPresent
- * @return true if added chunk
- */
- public boolean setChunk(PngChunk c, boolean overwriteIfPresent) {
- List<PngChunk> list = getMatching(c, false, true); // processed
- if (!list.isEmpty())
- throw new PngjException("chunk " + c.id + " already set ");
- list = getMatching(c, true, false); // queued
- if (!list.isEmpty()) {
- if (overwriteIfPresent) {
- for (PngChunk cx : list)
- removeChunk(cx);
- queueChunk(c, false, false);
- return true;
- }
- return false;
- }
- queueChunk(c, false, false);
- return true;
- }
-
- /**
- * returns only one chunk or null if nothing found - does not include queued
- *
- * If innerid!=null , the chunk is assumed to be PngChunkTextVar or PngChunkSPLT, and filtered by that id
- *
- * If more than one chunk (after filtering by inner id) is found, then an exception is thrown (failifMultiple=true)
- * or the last one is returned (failifMultiple=false)
- **/
- public PngChunk getChunk1(String id, String innerid, boolean failIfMultiple) {
- List<PngChunk> list = getChunks(id);
- if (list.isEmpty())
- return null;
- if (innerid != null) {
- List<PngChunk> list2 = new ArrayList<PngChunk>();
- for (PngChunk c : list) {
- if (c instanceof PngChunkTextVar)
- if (((PngChunkTextVar) c).getKey().equals(innerid))
- list2.add(c);
- if (c instanceof PngChunkSPLT)
- if (((PngChunkSPLT) c).getPalName().equals(innerid))
- list2.add(c);
- }
- list = list2;
- }
- if (list.isEmpty())
- return null;
- if (list.size() > 1 && failIfMultiple)
- throw new PngjException("unexpected multiple chunks id=" + id);
- return list.get(list.size() - 1);
- }
-
- public PngChunk getChunk1(String id) {
- return getChunk1(id, null, true);
- }
-
- public List<PngChunk> getChunks(String id) { // not including queued
- return getById(id, false, true);
- }
-
- private List<PngChunk> getMatching(PngChunk cnew, boolean includeQueued, boolean includeProcessed) {
- List<PngChunk> list = new ArrayList<PngChunk>();
- if (includeQueued)
- for (PngChunk c : getQueuedChunks())
- if (matches(cnew, c))
- list.add(c);
- if (includeProcessed)
- for (PngChunk c : getChunks())
- if (matches(cnew, c))
- list.add(c);
- return list;
- }
-
- /**
- * MY adhoc criteria: two chunks "match" if they have same id and (perhaps, if multiple are allowed) if the match
- * also in some "internal key" (eg: key for string values, palette for sPLT, etc)
- *
- * @return true if "matches"
- */
- public static boolean matches(PngChunk c2, PngChunk c1) {
- if (c1 == null || c2 == null || !c1.id.equals(c2.id))
- return false;
- // same id
- if (c1.getClass() != c2.getClass())
- return false; // should not happen
- if (!c2.allowsMultiple())
- return true;
- if (c1 instanceof PngChunkTextVar) {
- return ((PngChunkTextVar) c1).getKey().equals(((PngChunkTextVar) c2).getKey());
- }
- if (c1 instanceof PngChunkSPLT) {
- return ((PngChunkSPLT) c1).getPalName().equals(((PngChunkSPLT) c2).getPalName());
- }
- // unknown chunks that allow multiple? consider they don't match
- return false;
- }
-
- public String toString() {
- return "ChunkList: processed: " + chunks.size() + " queue: " + queuedChunks.size();
- }
-
- /**
- * for debugging
- */
- public String toStringFull() {
- StringBuilder sb = new StringBuilder(toString());
- sb.append("\n Processed:\n");
- for (PngChunk chunk : chunks) {
- sb.append(chunk).append(" G=" + chunk.getChunkGroup() + "\n");
- }
- if (!queuedChunks.isEmpty()) {
- sb.append(" Queued:\n");
- for (PngChunk chunk : chunks) {
- sb.append(chunk).append("\n");
- }
-
- }
- return sb.toString();
- }
-
-}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkLoadBehaviour.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkLoadBehaviour.java
index a3f85355c..1fa00380a 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkLoadBehaviour.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkLoadBehaviour.java
@@ -1,10 +1,28 @@
package jogamp.opengl.util.pngj.chunks;
+/**
+ * Defines gral strategy about what to do with ancillary (non-critical) chunks
+ * when reading
+ */
public enum ChunkLoadBehaviour {
- // what to do with non critical chunks when reading?
- LOAD_CHUNK_NEVER, /* ignore non-critical chunks */
- LOAD_CHUNK_KNOWN, /* load chunk if 'known' */
- LOAD_CHUNK_IF_SAFE, /* load chunk if 'known' or safe to copy */
- LOAD_CHUNK_ALWAYS /* load chunk always */
- ;
+ /**
+ * All non-critical chunks are skipped
+ */
+ LOAD_CHUNK_NEVER,
+ /**
+ * Ancillary chunks are loaded only if 'known' (registered with the
+ * factory).
+ */
+ LOAD_CHUNK_KNOWN,
+ /**
+ *
+ * Load chunk if "known" or "safe to copy".
+ */
+ LOAD_CHUNK_IF_SAFE,
+ /**
+ * Load all chunks. <br>
+ * Notice that other restrictions might apply, see
+ * PngReader.skipChunkMaxSize PngReader.skipChunkIds
+ */
+ LOAD_CHUNK_ALWAYS;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkPredicate.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkPredicate.java
new file mode 100644
index 000000000..4695ccf44
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkPredicate.java
@@ -0,0 +1,14 @@
+package jogamp.opengl.util.pngj.chunks;
+
+/**
+ * Decides if another chunk "matches", according to some criterion
+ */
+public interface ChunkPredicate {
+ /**
+ * The other chunk matches with this one
+ *
+ * @param chunk
+ * @return true if match
+ */
+ boolean match(PngChunk chunk);
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java
index 6770d5e95..0ac2dc6a0 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java
@@ -5,79 +5,112 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.CRC32;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjBadCrcException;
import jogamp.opengl.util.pngj.PngjOutputException;
/**
- * Wraps the raw chunk data Short lived object, to be created while serialing/deserializing Do not reuse it for
- * different chunks
- *
- * see http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
+ * Raw (physical) chunk.
+ * <p>
+ * Short lived object, to be created while serialing/deserializing Do not reuse
+ * it for different chunks. <br>
+ * See http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
*/
public class ChunkRaw {
+ /**
+ * The length counts only the data field, not itself, the chunk type code,
+ * or the CRC. Zero is a valid length. Although encoders and decoders should
+ * treat the length as unsigned, its value must not exceed 231-1 bytes.
+ */
public final int len;
- public final byte[] idbytes = new byte[4]; // 4 bytes
- public byte[] data = null; // crc not included
+
+ /**
+ * A 4-byte chunk type code. uppercase and lowercase ASCII letters
+ */
+ public final byte[] idbytes = new byte[4];
+
+ /**
+ * The data bytes appropriate to the chunk type, if any. This field can be
+ * of zero length. Does not include crc
+ */
+ public byte[] data = null;
+ /**
+ * A 4-byte CRC (Cyclic Redundancy Check) calculated on the preceding bytes
+ * in the chunk, including the chunk type code and chunk data fields, but
+ * not including the length field.
+ */
private int crcval = 0;
- // public int offset=-1; // only for read chunks - informational
- public ChunkRaw(int len, byte[] idbytes, boolean alloc) {
+ /**
+ * @param len
+ * : data len
+ * @param idbytes
+ * : chunk type (deep copied)
+ * @param alloc
+ * : it true, the data array will be allocced
+ */
+ public ChunkRaw(final int len, final byte[] idbytes, final boolean alloc) {
this.len = len;
System.arraycopy(idbytes, 0, this.idbytes, 0, 4);
if (alloc)
allocData();
}
- public void writeChunk(OutputStream os) {
- if (idbytes.length != 4)
- throw new PngjOutputException("bad chunkid [" + ChunkHelper.toString(idbytes) + "]");
- computeCrc();
- PngHelper.writeInt4(os, len);
- PngHelper.writeBytes(os, idbytes);
- if (len > 0)
- PngHelper.writeBytes(os, data, 0, len);
- // System.err.println("writing chunk " + this.toString() + "crc=" + crcval);
-
- PngHelper.writeInt4(os, crcval);
+ private void allocData() {
+ if (data == null || data.length < len)
+ data = new byte[len];
}
/**
- * called after setting data, before writing to os
+ * this is called after setting data, before writing to os
*/
- private void computeCrc() {
- CRC32 crcengine = PngHelper.getCRC();
+ private int computeCrc() {
+ final CRC32 crcengine = PngHelperInternal.getCRC();
crcengine.reset();
crcengine.update(idbytes, 0, 4);
if (len > 0)
crcengine.update(data, 0, len); //
- crcval = (int) crcengine.getValue();
+ return (int) crcengine.getValue();
}
- public String toString() {
- return "chunkid=" + ChunkHelper.toString(idbytes) + " len=" + len;
+ /**
+ * Computes the CRC and writes to the stream. If error, a
+ * PngjOutputException is thrown
+ */
+ public void writeChunk(final OutputStream os) {
+ if (idbytes.length != 4)
+ throw new PngjOutputException("bad chunkid [" + ChunkHelper.toString(idbytes) + "]");
+ crcval = computeCrc();
+ PngHelperInternal.writeInt4(os, len);
+ PngHelperInternal.writeBytes(os, idbytes);
+ if (len > 0)
+ PngHelperInternal.writeBytes(os, data, 0, len);
+ PngHelperInternal.writeInt4(os, crcval);
}
/**
- * position before: just after chunk id. positon after: after crc Data should be already allocated. Checks CRC
- * Return number of byte read.
+ * position before: just after chunk id. positon after: after crc Data
+ * should be already allocated. Checks CRC Return number of byte read.
*/
- public int readChunkData(InputStream is) {
- PngHelper.readBytes(is, data, 0, len);
- int crcori = PngHelper.readInt4(is);
- computeCrc();
- if (crcori != crcval)
- throw new PngjBadCrcException("crc invalid for chunk " + toString() + " calc=" + crcval + " read=" + crcori);
+ public int readChunkData(final InputStream is, final boolean checkCrc) {
+ PngHelperInternal.readBytes(is, data, 0, len);
+ crcval = PngHelperInternal.readInt4(is);
+ if (checkCrc) {
+ final int crc = computeCrc();
+ if (crc != crcval)
+ throw new PngjBadCrcException("chunk: " + this + " crc calc=" + crc + " read=" + crcval);
+ }
return len + 4;
}
- public ByteArrayInputStream getAsByteStream() { // only the data
+ ByteArrayInputStream getAsByteStream() { // only the data
return new ByteArrayInputStream(data);
}
- private void allocData() {
- if (data == null || data.length < len)
- data = new byte[len];
+ @Override
+ public String toString() {
+ return "chunkid=" + ChunkHelper.toString(idbytes) + " len=" + len;
}
+
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunksList.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunksList.java
new file mode 100644
index 000000000..f5a920e73
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunksList.java
@@ -0,0 +1,181 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngjException;
+
+/**
+ * All chunks that form an image, read or to be written.
+ * <p>
+ * chunks include all chunks, but IDAT is a single pseudo chunk without data
+ **/
+public class ChunksList {
+ // ref: http://www.w3.org/TR/PNG/#table53
+ public static final int CHUNK_GROUP_0_IDHR = 0; // required - single
+ public static final int CHUNK_GROUP_1_AFTERIDHR = 1; // optional - multiple
+ public static final int CHUNK_GROUP_2_PLTE = 2; // optional - single
+ public static final int CHUNK_GROUP_3_AFTERPLTE = 3; // optional - multple
+ public static final int CHUNK_GROUP_4_IDAT = 4; // required (single pseudo chunk)
+ public static final int CHUNK_GROUP_5_AFTERIDAT = 5; // optional - multple
+ public static final int CHUNK_GROUP_6_END = 6; // only 1 chunk - requried
+
+ /**
+ * All chunks, read (or written)
+ *
+ * But IDAT is a single pseudo chunk without data
+ */
+ protected List<PngChunk> chunks = new ArrayList<PngChunk>();
+
+ final ImageInfo imageInfo; // only required for writing
+
+ public ChunksList(final ImageInfo imfinfo) {
+ this.imageInfo = imfinfo;
+ }
+
+ /**
+ * Keys of processed (read or writen) chunks
+ *
+ * @return key:chunk id, val: number of occurrences
+ */
+ public HashMap<String, Integer> getChunksKeys() {
+ final HashMap<String, Integer> ck = new HashMap<String, Integer>();
+ for (final PngChunk c : chunks) {
+ ck.put(c.id, ck.containsKey(c.id) ? ck.get(c.id) + 1 : 1);
+ }
+ return ck;
+ }
+
+ /**
+ * Returns a copy of the list (but the chunks are not copied) <b> This
+ * should not be used for general metadata handling
+ */
+ public ArrayList<PngChunk> getChunks() {
+ return new ArrayList<PngChunk>(chunks);
+ }
+
+ protected static List<PngChunk> getXById(final List<PngChunk> list, final String id, final String innerid) {
+ if (innerid == null)
+ return ChunkHelper.filterList(list, new ChunkPredicate() {
+ @Override
+ public boolean match(final PngChunk c) {
+ return c.id.equals(id);
+ }
+ });
+ else
+ return ChunkHelper.filterList(list, new ChunkPredicate() {
+ @Override
+ public boolean match(final PngChunk c) {
+ if (!c.id.equals(id))
+ return false;
+ if (c instanceof PngChunkTextVar && !((PngChunkTextVar) c).getKey().equals(innerid))
+ return false;
+ if (c instanceof PngChunkSPLT && !((PngChunkSPLT) c).getPalName().equals(innerid))
+ return false;
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Adds chunk in next position. This is used onyl by the pngReader
+ */
+ public void appendReadChunk(final PngChunk chunk, final int chunkGroup) {
+ chunk.setChunkGroup(chunkGroup);
+ chunks.add(chunk);
+ }
+
+ /**
+ * All chunks with this ID
+ *
+ * @param id
+ * @return List, empty if none
+ */
+ public List<? extends PngChunk> getById(final String id) {
+ return getById(id, null);
+ }
+
+ /**
+ * If innerid!=null and the chunk is PngChunkTextVar or PngChunkSPLT, it's
+ * filtered by that id
+ *
+ * @param id
+ * @return innerid Only used for text and SPLT chunks
+ * @return List, empty if none
+ */
+ public List<? extends PngChunk> getById(final String id, final String innerid) {
+ return getXById(chunks, id, innerid);
+ }
+
+ /**
+ * Returns only one chunk
+ *
+ * @param id
+ * @return First chunk found, null if not found
+ */
+ public PngChunk getById1(final String id) {
+ return getById1(id, false);
+ }
+
+ /**
+ * Returns only one chunk or null if nothing found - does not include queued
+ * <p>
+ * If more than one chunk is found, then an exception is thrown
+ * (failifMultiple=true or chunk is single) or the last one is returned
+ * (failifMultiple=false)
+ **/
+ public PngChunk getById1(final String id, final boolean failIfMultiple) {
+ return getById1(id, null, failIfMultiple);
+ }
+
+ /**
+ * Returns only one chunk or null if nothing found - does not include queued
+ * <p>
+ * If more than one chunk (after filtering by inner id) is found, then an
+ * exception is thrown (failifMultiple=true or chunk is single) or the last
+ * one is returned (failifMultiple=false)
+ **/
+ public PngChunk getById1(final String id, final String innerid, final boolean failIfMultiple) {
+ final List<? extends PngChunk> list = getById(id, innerid);
+ if (list.isEmpty())
+ return null;
+ if (list.size() > 1 && (failIfMultiple || !list.get(0).allowsMultiple()))
+ throw new PngjException("unexpected multiple chunks id=" + id);
+ return list.get(list.size() - 1);
+ }
+
+ /**
+ * Finds all chunks "equivalent" to this one
+ *
+ * @param c2
+ * @return Empty if nothing found
+ */
+ public List<PngChunk> getEquivalent(final PngChunk c2) {
+ return ChunkHelper.filterList(chunks, new ChunkPredicate() {
+ @Override
+ public boolean match(final PngChunk c) {
+ return ChunkHelper.equivalent(c, c2);
+ }
+ });
+ }
+
+ @Override
+ public String toString() {
+ return "ChunkList: read: " + chunks.size();
+ }
+
+ /**
+ * for debugging
+ */
+ public String toStringFull() {
+ final StringBuilder sb = new StringBuilder(toString());
+ sb.append("\n Read:\n");
+ for (final PngChunk chunk : chunks) {
+ sb.append(chunk).append(" G=" + chunk.getChunkGroup() + "\n");
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunksListForWrite.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunksListForWrite.java
new file mode 100644
index 000000000..6ad61f8e2
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunksListForWrite.java
@@ -0,0 +1,176 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngjException;
+import jogamp.opengl.util.pngj.PngjOutputException;
+
+public class ChunksListForWrite extends ChunksList {
+
+ /**
+ * chunks not yet writen - does not include IHDR, IDAT, END, perhaps yes
+ * PLTE
+ */
+ private final List<PngChunk> queuedChunks = new ArrayList<PngChunk>();
+
+ // redundant, just for eficciency
+ private final HashMap<String, Integer> alreadyWrittenKeys = new HashMap<String, Integer>();
+
+ public ChunksListForWrite(final ImageInfo imfinfo) {
+ super(imfinfo);
+ }
+
+ /**
+ * Same as getById(), but looking in the queued chunks
+ */
+ public List<? extends PngChunk> getQueuedById(final String id) {
+ return getQueuedById(id, null);
+ }
+
+ /**
+ * Same as getById(), but looking in the queued chunks
+ */
+ public List<? extends PngChunk> getQueuedById(final String id, final String innerid) {
+ return getXById(queuedChunks, id, innerid);
+ }
+
+ /**
+ * Same as getById1(), but looking in the queued chunks
+ **/
+ public PngChunk getQueuedById1(final String id, final String innerid, final boolean failIfMultiple) {
+ final List<? extends PngChunk> list = getQueuedById(id, innerid);
+ if (list.isEmpty())
+ return null;
+ if (list.size() > 1 && (failIfMultiple || !list.get(0).allowsMultiple()))
+ throw new PngjException("unexpected multiple chunks id=" + id);
+ return list.get(list.size() - 1);
+ }
+
+ /**
+ * Same as getById1(), but looking in the queued chunks
+ **/
+ public PngChunk getQueuedById1(final String id, final boolean failIfMultiple) {
+ return getQueuedById1(id, null, failIfMultiple);
+ }
+
+ /**
+ * Same as getById1(), but looking in the queued chunks
+ **/
+ public PngChunk getQueuedById1(final String id) {
+ return getQueuedById1(id, false);
+ }
+
+ /**
+ * Remove Chunk: only from queued
+ *
+ * WARNING: this depends on c.equals() implementation, which is
+ * straightforward for SingleChunks. For MultipleChunks, it will normally
+ * check for reference equality!
+ */
+ public boolean removeChunk(final PngChunk c) {
+ return queuedChunks.remove(c);
+ }
+
+ /**
+ * Adds chunk to queue
+ *
+ * Does not check for duplicated or anything
+ *
+ * @param c
+ */
+ public boolean queue(final PngChunk c) {
+ queuedChunks.add(c);
+ return true;
+ }
+
+ /**
+ * this should be called only for ancillary chunks and PLTE (groups 1 - 3 -
+ * 5)
+ **/
+ private static boolean shouldWrite(final PngChunk c, final int currentGroup) {
+ if (currentGroup == CHUNK_GROUP_2_PLTE)
+ return c.id.equals(ChunkHelper.PLTE);
+ if (currentGroup % 2 == 0)
+ throw new PngjOutputException("bad chunk group?");
+ int minChunkGroup, maxChunkGroup;
+ if (c.getOrderingConstraint().mustGoBeforePLTE())
+ minChunkGroup = maxChunkGroup = ChunksList.CHUNK_GROUP_1_AFTERIDHR;
+ else if (c.getOrderingConstraint().mustGoBeforeIDAT()) {
+ maxChunkGroup = ChunksList.CHUNK_GROUP_3_AFTERPLTE;
+ minChunkGroup = c.getOrderingConstraint().mustGoAfterPLTE() ? ChunksList.CHUNK_GROUP_3_AFTERPLTE
+ : ChunksList.CHUNK_GROUP_1_AFTERIDHR;
+ } else {
+ maxChunkGroup = ChunksList.CHUNK_GROUP_5_AFTERIDAT;
+ minChunkGroup = ChunksList.CHUNK_GROUP_1_AFTERIDHR;
+ }
+
+ int preferred = maxChunkGroup;
+ if (c.hasPriority())
+ preferred = minChunkGroup;
+ if (ChunkHelper.isUnknown(c) && c.getChunkGroup() > 0)
+ preferred = c.getChunkGroup();
+ if (currentGroup == preferred)
+ return true;
+ if (currentGroup > preferred && currentGroup <= maxChunkGroup)
+ return true;
+ return false;
+ }
+
+ public int writeChunks(final OutputStream os, final int currentGroup) {
+ int cont = 0;
+ final Iterator<PngChunk> it = queuedChunks.iterator();
+ while (it.hasNext()) {
+ final PngChunk c = it.next();
+ if (!shouldWrite(c, currentGroup))
+ continue;
+ if (ChunkHelper.isCritical(c.id) && !c.id.equals(ChunkHelper.PLTE))
+ throw new PngjOutputException("bad chunk queued: " + c);
+ if (alreadyWrittenKeys.containsKey(c.id) && !c.allowsMultiple())
+ throw new PngjOutputException("duplicated chunk does not allow multiple: " + c);
+ c.write(os);
+ chunks.add(c);
+ alreadyWrittenKeys.put(c.id, alreadyWrittenKeys.containsKey(c.id) ? alreadyWrittenKeys.get(c.id) + 1 : 1);
+ c.setChunkGroup(currentGroup);
+ it.remove();
+ cont++;
+ }
+ return cont;
+ }
+
+ /**
+ * warning: this is NOT a copy, do not modify
+ */
+ public List<PngChunk> getQueuedChunks() {
+ return queuedChunks;
+ }
+
+ @Override
+ public String toString() {
+ return "ChunkList: written: " + chunks.size() + " queue: " + queuedChunks.size();
+ }
+
+ /**
+ * for debugging
+ */
+ @Override
+ public String toStringFull() {
+ final StringBuilder sb = new StringBuilder(toString());
+ sb.append("\n Written:\n");
+ for (final PngChunk chunk : chunks) {
+ sb.append(chunk).append(" G=" + chunk.getChunkGroup() + "\n");
+ }
+ if (!queuedChunks.isEmpty()) {
+ sb.append(" Queued:\n");
+ for (final PngChunk chunk : queuedChunks) {
+ sb.append(chunk).append("\n");
+ }
+
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunk.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunk.java
index 2df9fd1f3..eba218fe3 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunk.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunk.java
@@ -6,26 +6,91 @@ import java.util.HashMap;
import java.util.Map;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngjException;
+import jogamp.opengl.util.pngj.PngjExceptionInternal;
-
-// see http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
+/**
+ * Represents a instance of a PNG chunk.
+ * <p>
+ * See <a
+ * href="http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html">http://www
+ * .libpng.org/pub/png/spec/1.2/PNG-Chunks .html</a> </a>
+ * <p>
+ * Concrete classes should extend {@link PngChunkSingle} or
+ * {@link PngChunkMultiple}
+ * <p>
+ * Note that some methods/fields are type-specific (getOrderingConstraint(),
+ * allowsMultiple()),<br>
+ * some are 'almost' type-specific (id,crit,pub,safe; the exception is
+ * PngUKNOWN), <br>
+ * and the rest are instance-specific
+ */
public abstract class PngChunk {
- public final String id; // 4 letters
+ /**
+ * Chunk-id: 4 letters
+ */
+ public final String id;
+ /**
+ * Autocomputed at creation time
+ */
public final boolean crit, pub, safe;
- private int lenori = -1; // merely informational, for read chunks
- private boolean writePriority = false; // for queued chunks
protected final ImageInfo imgInfo;
- private int chunkGroup = -1; // chunk group where it was read or writen
+ /**
+ * Possible ordering constraint for a PngChunk type -only relevant for
+ * ancillary chunks. Theoretically, there could be more general constraints,
+ * but these cover the constraints for standard chunks.
+ */
+ public enum ChunkOrderingConstraint {
+ /**
+ * no ordering constraint
+ */
+ NONE,
+ /**
+ * Must go before PLTE (and hence, also before IDAT)
+ */
+ BEFORE_PLTE_AND_IDAT,
+ /**
+ * Must go after PLTE but before IDAT
+ */
+ AFTER_PLTE_BEFORE_IDAT,
+ /**
+ * Must before IDAT (before or after PLTE)
+ */
+ BEFORE_IDAT,
+ /**
+ * Does not apply
+ */
+ NA;
+
+ public boolean mustGoBeforePLTE() {
+ return this == BEFORE_PLTE_AND_IDAT;
+ }
+
+ public boolean mustGoBeforeIDAT() {
+ return this == BEFORE_IDAT || this == BEFORE_PLTE_AND_IDAT || this == AFTER_PLTE_BEFORE_IDAT;
+ }
+
+ public boolean mustGoAfterPLTE() {
+ return this == AFTER_PLTE_BEFORE_IDAT;
+ }
+ }
+
+ private boolean priority = false; // For writing. Queued chunks with high priority will be written as soon as
+ // possible
+
+ protected int chunkGroup = -1; // chunk group where it was read or writen
+ protected int length = -1; // merely informational, for read chunks
+ protected long offset = 0; // merely informational, for read chunks
/**
- * This static map defines which PngChunk class correspond to which ChunkID The client can add other chunks to this
- * map statically, before reading
+ * This static map defines which PngChunk class correspond to which ChunkID
+ * <p>
+ * The client can add other chunks to this map statically, before reading an
+ * image, calling PngChunk.factoryRegister(id,class)
*/
- public final static Map<String, Class<? extends PngChunk>> factoryMap = new HashMap<String, Class<? extends PngChunk>>();
+ private final static Map<String, Class<? extends PngChunk>> factoryMap = new HashMap<String, Class<? extends PngChunk>>();
static {
factoryMap.put(ChunkHelper.IDAT, PngChunkIDAT.class);
factoryMap.put(ChunkHelper.IHDR, PngChunkIHDR.class);
@@ -45,9 +110,38 @@ public abstract class PngChunk {
factoryMap.put(ChunkHelper.sRGB, PngChunkSRGB.class);
factoryMap.put(ChunkHelper.hIST, PngChunkHIST.class);
factoryMap.put(ChunkHelper.sPLT, PngChunkSPLT.class);
+ // extended
+ factoryMap.put(PngChunkOFFS.ID, PngChunkOFFS.class);
+ factoryMap.put(PngChunkSTER.ID, PngChunkSTER.class);
+ }
+
+ /**
+ * Registers a chunk-id (4 letters) to be associated with a PngChunk class
+ * <p>
+ * This method should be called by user code that wants to add some chunks
+ * (not implmemented in this library) to the factory, so that the PngReader
+ * knows about it.
+ */
+ public static void factoryRegister(final String chunkId, final Class<? extends PngChunk> chunkClass) {
+ factoryMap.put(chunkId, chunkClass);
}
- protected PngChunk(String id, ImageInfo imgInfo) {
+ /**
+ * True if the chunk-id type is known.
+ * <p>
+ * A chunk is known if we recognize its class, according with
+ * <code>factoryMap</code>
+ * <p>
+ * This is not necessarily the same as being "STANDARD", or being
+ * implemented in this library
+ * <p>
+ * Unknown chunks will be parsed as instances of {@link PngChunkUNKNOWN}
+ */
+ public static boolean isKnown(final String id) {
+ return factoryMap.containsKey(id);
+ }
+
+ protected PngChunk(final String id, final ImageInfo imgInfo) {
this.id = id;
this.imgInfo = imgInfo;
this.crit = ChunkHelper.isCritical(id);
@@ -55,98 +149,136 @@ public abstract class PngChunk {
this.safe = ChunkHelper.isSafeToCopy(id);
}
- public abstract ChunkRaw createChunk();
-
- public abstract void parseFromChunk(ChunkRaw c);
-
- // override to make deep copy from read data to write
- public abstract void cloneDataFromRead(PngChunk other);
-
- @SuppressWarnings("unchecked")
- public static <T extends PngChunk> T cloneChunk(T chunk, ImageInfo info) {
- PngChunk cn = factoryFromId(chunk.id, info);
- if (cn.getClass() != chunk.getClass())
- throw new PngjException("bad class cloning chunk: " + cn.getClass() + " " + chunk.getClass());
- cn.cloneDataFromRead(chunk);
- return (T) cn;
- }
-
- public static PngChunk factory(ChunkRaw chunk, ImageInfo info) {
- PngChunk c = factoryFromId(ChunkHelper.toString(chunk.idbytes), info);
- c.lenori = chunk.len;
- c.parseFromChunk(chunk);
+ /**
+ * This factory creates the corresponding chunk and parses the raw chunk.
+ * This is used when reading.
+ */
+ public static PngChunk factory(final ChunkRaw chunk, final ImageInfo info) {
+ final PngChunk c = factoryFromId(ChunkHelper.toString(chunk.idbytes), info);
+ c.length = chunk.len;
+ c.parseFromRaw(chunk);
return c;
}
- public static PngChunk factoryFromId(String cid, ImageInfo info) {
+ /**
+ * Creates one new blank chunk of the corresponding type, according to
+ * factoryMap (PngChunkUNKNOWN if not known)
+ */
+ public static PngChunk factoryFromId(final String cid, final ImageInfo info) {
PngChunk chunk = null;
try {
- Class<? extends PngChunk> cla = factoryMap.get(cid);
+ final Class<? extends PngChunk> cla = factoryMap.get(cid);
if (cla != null) {
- Constructor<? extends PngChunk> constr = cla.getConstructor(ImageInfo.class);
+ final Constructor<? extends PngChunk> constr = cla.getConstructor(ImageInfo.class);
chunk = constr.newInstance(info);
}
- } catch (Exception e) {
- // this can happend for unkown chunks
+ } catch (final Exception e) {
+ // this can happen for unkown chunks
}
if (chunk == null)
chunk = new PngChunkUNKNOWN(cid, info);
return chunk;
}
- protected ChunkRaw createEmptyChunk(int len, boolean alloc) {
- ChunkRaw c = new ChunkRaw(len, ChunkHelper.toBytes(id), alloc);
+ protected final ChunkRaw createEmptyChunk(final int len, final boolean alloc) {
+ final ChunkRaw c = new ChunkRaw(len, ChunkHelper.toBytes(id), alloc);
return c;
}
- @Override
- public String toString() {
- return "chunk id= " + id + " (" + lenori + ") c=" + getClass().getSimpleName();
+ /**
+ * Makes a clone (deep copy) calling {@link #cloneDataFromRead(PngChunk)}
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends PngChunk> T cloneChunk(final T chunk, final ImageInfo info) {
+ final PngChunk cn = factoryFromId(chunk.id, info);
+ if (cn.getClass() != chunk.getClass())
+ throw new PngjExceptionInternal("bad class cloning chunk: " + cn.getClass() + " " + chunk.getClass());
+ cn.cloneDataFromRead(chunk);
+ return (T) cn;
}
- void setPriority(boolean highPrioriy) {
- writePriority = highPrioriy;
+ /**
+ * In which "chunkGroup" (see {@link ChunksList}for definition) this chunks
+ * instance was read or written.
+ * <p>
+ * -1 if not read or written (eg, queued)
+ */
+ final public int getChunkGroup() {
+ return chunkGroup;
}
- void write(OutputStream os) {
- ChunkRaw c = createChunk();
- if (c == null)
- throw new PngjException("null chunk ! creation failed for " + this);
- c.writeChunk(os);
+ /**
+ * @see #getChunkGroup()
+ */
+ final public void setChunkGroup(final int chunkGroup) {
+ this.chunkGroup = chunkGroup;
}
- public boolean isWritePriority() {
- return writePriority;
+ public boolean hasPriority() {
+ return priority;
}
- /** must be overriden - only relevant for ancillary chunks */
- public boolean allowsMultiple() {
- return false; // override if allows multiple ocurrences
+ public void setPriority(final boolean priority) {
+ this.priority = priority;
}
- /** mustGoBeforeXX/After must be overriden - only relevant for ancillary chunks */
- public boolean mustGoBeforeIDAT() {
- return false;
+ final void write(final OutputStream os) {
+ final ChunkRaw c = createRawChunk();
+ if (c == null)
+ throw new PngjExceptionInternal("null chunk ! creation failed for " + this);
+ c.writeChunk(os);
}
- public boolean mustGoBeforePLTE() {
- return false;
+ public int getLength() {
+ return length;
}
- public boolean mustGoAfterPLTE() {
- return false;
- }
+ /*
+ * public void setLength(int length) { this.length = length; }
+ */
- static boolean isKnown(String id) {
- return factoryMap.containsKey(id);
+ public long getOffset() {
+ return offset;
}
- public int getChunkGroup() {
- return chunkGroup;
+ public void setOffset(final long offset) {
+ this.offset = offset;
}
- public void setChunkGroup(int chunkGroup) {
- this.chunkGroup = chunkGroup;
+ /**
+ * Creates the physical chunk. This is used when writing (serialization).
+ * Each particular chunk class implements its own logic.
+ *
+ * @return A newly allocated and filled raw chunk
+ */
+ public abstract ChunkRaw createRawChunk();
+
+ /**
+ * Parses raw chunk and fill inside data. This is used when reading
+ * (deserialization). Each particular chunk class implements its own logic.
+ */
+ public abstract void parseFromRaw(ChunkRaw c);
+
+ /**
+ * Makes a copy of the chunk.
+ * <p>
+ * This is used when copying chunks from a reader to a writer
+ * <p>
+ * It should normally be a deep copy, and after the cloning
+ * this.equals(other) should return true
+ */
+ public abstract void cloneDataFromRead(PngChunk other);
+
+ public abstract boolean allowsMultiple(); // this is implemented in PngChunkMultiple/PngChunSingle
+
+ /**
+ * see {@link ChunkOrderingConstraint}
+ */
+ public abstract ChunkOrderingConstraint getOrderingConstraint();
+
+ @Override
+ public String toString() {
+ return "chunk id= " + id + " (len=" + length + " offset=" + offset + ") c=" + getClass().getSimpleName();
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java
index 51bbcb832..191278dbc 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java
@@ -1,67 +1,66 @@
package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
-/*
+/**
+ * bKGD Chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11bKGD
+ * <p>
+ * this chunk structure depends on the image type
*/
-public class PngChunkBKGD extends PngChunk {
- // http://www.w3.org/TR/PNG/#11bKGD
- // this chunk structure depends on the image type
+public class PngChunkBKGD extends PngChunkSingle {
+ public final static String ID = ChunkHelper.bKGD;
// only one of these is meaningful
private int gray;
private int red, green, blue;
private int paletteIndex;
- public PngChunkBKGD(ImageInfo info) {
+ public PngChunkBKGD(final ImageInfo info) {
super(ChunkHelper.bKGD, info);
}
@Override
- public boolean mustGoBeforeIDAT() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.AFTER_PLTE_BEFORE_IDAT;
}
@Override
- public boolean mustGoAfterPLTE() {
- return true;
- }
-
- @Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
ChunkRaw c = null;
if (imgInfo.greyscale) {
c = createEmptyChunk(2, true);
- PngHelper.writeInt2tobytes(gray, c.data, 0);
+ PngHelperInternal.writeInt2tobytes(gray, c.data, 0);
} else if (imgInfo.indexed) {
c = createEmptyChunk(1, true);
c.data[0] = (byte) paletteIndex;
} else {
c = createEmptyChunk(6, true);
- PngHelper.writeInt2tobytes(red, c.data, 0);
- PngHelper.writeInt2tobytes(green, c.data, 0);
- PngHelper.writeInt2tobytes(blue, c.data, 0);
+ PngHelperInternal.writeInt2tobytes(red, c.data, 0);
+ PngHelperInternal.writeInt2tobytes(green, c.data, 0);
+ PngHelperInternal.writeInt2tobytes(blue, c.data, 0);
}
return c;
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
if (imgInfo.greyscale) {
- gray = PngHelper.readInt2fromBytes(c.data, 0);
+ gray = PngHelperInternal.readInt2fromBytes(c.data, 0);
} else if (imgInfo.indexed) {
- paletteIndex = (int) (c.data[0] & 0xff);
+ paletteIndex = c.data[0] & 0xff;
} else {
- red = PngHelper.readInt2fromBytes(c.data, 0);
- green = PngHelper.readInt2fromBytes(c.data, 2);
- blue = PngHelper.readInt2fromBytes(c.data, 4);
+ red = PngHelperInternal.readInt2fromBytes(c.data, 0);
+ green = PngHelperInternal.readInt2fromBytes(c.data, 2);
+ blue = PngHelperInternal.readInt2fromBytes(c.data, 4);
}
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkBKGD otherx = (PngChunkBKGD) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkBKGD otherx = (PngChunkBKGD) other;
gray = otherx.gray;
red = otherx.red;
green = otherx.red;
@@ -71,10 +70,10 @@ public class PngChunkBKGD extends PngChunk {
/**
* Set gray value (0-255 if bitdept=8)
- *
+ *
* @param gray
*/
- public void setGray(int gray) {
+ public void setGray(final int gray) {
if (!imgInfo.greyscale)
throw new PngjException("only gray images support this");
this.gray = gray;
@@ -88,9 +87,9 @@ public class PngChunkBKGD extends PngChunk {
/**
* Set pallette index
- *
+ *
*/
- public void setPaletteIndex(int i) {
+ public void setPaletteIndex(final int i) {
if (!imgInfo.indexed)
throw new PngjException("only indexed (pallete) images support this");
this.paletteIndex = i;
@@ -104,9 +103,9 @@ public class PngChunkBKGD extends PngChunk {
/**
* Set rgb values
- *
+ *
*/
- public void setRGB(int r, int g, int b) {
+ public void setRGB(final int r, final int g, final int b) {
if (imgInfo.greyscale || imgInfo.indexed)
throw new PngjException("only rgb or rgba images support this");
red = r;
@@ -119,4 +118,5 @@ public class PngChunkBKGD extends PngChunk {
throw new PngjException("only rgb or rgba images support this");
return new int[] { red, green, blue };
}
+
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java
index 4380761c7..8bdd7b4c0 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java
@@ -1,64 +1,64 @@
package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
-/*
+/**
+ * cHRM chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11cHRM
*/
-public class PngChunkCHRM extends PngChunk {
+public class PngChunkCHRM extends PngChunkSingle {
+ public final static String ID = ChunkHelper.cHRM;
+
// http://www.w3.org/TR/PNG/#11cHRM
private double whitex, whitey;
private double redx, redy;
private double greenx, greeny;
private double bluex, bluey;
- public PngChunkCHRM(ImageInfo info) {
- super(ChunkHelper.cHRM, info);
- }
-
- @Override
- public boolean mustGoBeforeIDAT() {
- return true;
+ public PngChunkCHRM(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public boolean mustGoBeforePLTE() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.AFTER_PLTE_BEFORE_IDAT;
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
ChunkRaw c = null;
c = createEmptyChunk(32, true);
- PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(whitex), c.data, 0);
- PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(whitey), c.data, 4);
- PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(redx), c.data, 8);
- PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(redy), c.data, 12);
- PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(greenx), c.data, 16);
- PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(greeny), c.data, 20);
- PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(bluex), c.data, 24);
- PngHelper.writeInt4tobytes(PngHelper.doubleToInt100000(bluey), c.data, 28);
+ PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(whitex), c.data, 0);
+ PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(whitey), c.data, 4);
+ PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(redx), c.data, 8);
+ PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(redy), c.data, 12);
+ PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(greenx), c.data, 16);
+ PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(greeny), c.data, 20);
+ PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(bluex), c.data, 24);
+ PngHelperInternal.writeInt4tobytes(PngHelperInternal.doubleToInt100000(bluey), c.data, 28);
return c;
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
if (c.len != 32)
throw new PngjException("bad chunk " + c);
- whitex = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 0));
- whitey = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 4));
- redx = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 8));
- redy = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 12));
- greenx = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 16));
- greeny = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 20));
- bluex = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 24));
- bluey = PngHelper.intToDouble100000(PngHelper.readInt4fromBytes(c.data, 28));
+ whitex = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 0));
+ whitey = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 4));
+ redx = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 8));
+ redy = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 12));
+ greenx = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 16));
+ greeny = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 20));
+ bluex = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 24));
+ bluey = PngHelperInternal.intToDouble100000(PngHelperInternal.readInt4fromBytes(c.data, 28));
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkCHRM otherx = (PngChunkCHRM) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkCHRM otherx = (PngChunkCHRM) other;
whitex = otherx.whitex;
whitey = otherx.whitex;
redx = otherx.redx;
@@ -69,8 +69,8 @@ public class PngChunkCHRM extends PngChunk {
bluey = otherx.bluey;
}
- public void setChromaticities(double whitex, double whitey, double redx, double redy, double greenx, double greeny,
- double bluex, double bluey) {
+ public void setChromaticities(final double whitex, final double whitey, final double redx, final double redy, final double greenx, final double greeny,
+ final double bluex, final double bluey) {
this.whitex = whitex;
this.redx = redx;
this.greenx = greenx;
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java
index 184ee9ffa..6b627326c 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java
@@ -1,47 +1,47 @@
package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
-/*
+/**
+ * gAMA chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11gAMA
*/
-public class PngChunkGAMA extends PngChunk {
+public class PngChunkGAMA extends PngChunkSingle {
+ public final static String ID = ChunkHelper.gAMA;
+
// http://www.w3.org/TR/PNG/#11gAMA
private double gamma;
- public PngChunkGAMA(ImageInfo info) {
- super(ChunkHelper.gAMA, info);
- }
-
- @Override
- public boolean mustGoBeforeIDAT() {
- return true;
+ public PngChunkGAMA(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public boolean mustGoBeforePLTE() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_PLTE_AND_IDAT;
}
@Override
- public ChunkRaw createChunk() {
- ChunkRaw c = createEmptyChunk(4, true);
- int g = (int) (gamma * 100000 + 0.5);
- PngHelper.writeInt4tobytes(g, c.data, 0);
+ public ChunkRaw createRawChunk() {
+ final ChunkRaw c = createEmptyChunk(4, true);
+ final int g = (int) (gamma * 100000 + 0.5);
+ PngHelperInternal.writeInt4tobytes(g, c.data, 0);
return c;
}
@Override
- public void parseFromChunk(ChunkRaw chunk) {
+ public void parseFromRaw(final ChunkRaw chunk) {
if (chunk.len != 4)
throw new PngjException("bad chunk " + chunk);
- int g = PngHelper.readInt4fromBytes(chunk.data, 0);
- gamma = ((double) g) / 100000.0;
+ final int g = PngHelperInternal.readInt4fromBytes(chunk.data, 0);
+ gamma = (g) / 100000.0;
}
@Override
- public void cloneDataFromRead(PngChunk other) {
+ public void cloneDataFromRead(final PngChunk other) {
gamma = ((PngChunkGAMA) other).gamma;
}
@@ -49,7 +49,7 @@ public class PngChunkGAMA extends PngChunk {
return gamma;
}
- public void setGamma(double gamma) {
+ public void setGamma(final double gamma) {
this.gamma = gamma;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java
index b0f02ea37..4a4832d3b 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java
@@ -1,57 +1,55 @@
package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
-/*
+/**
+ * hIST chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11hIST <br>
+ * only for palette images
*/
-public class PngChunkHIST extends PngChunk {
- // http://www.w3.org/TR/PNG/#11hIST
- // only for palette images
+public class PngChunkHIST extends PngChunkSingle {
+ public final static String ID = ChunkHelper.hIST;
private int[] hist = new int[0]; // should have same lenght as palette
- public PngChunkHIST(ImageInfo info) {
- super(ChunkHelper.hIST, info);
+ public PngChunkHIST(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public boolean mustGoBeforeIDAT() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.AFTER_PLTE_BEFORE_IDAT;
}
@Override
- public boolean mustGoAfterPLTE() {
- return true;
- }
-
- @Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
if (!imgInfo.indexed)
throw new PngjException("only indexed images accept a HIST chunk");
- int nentries = c.data.length / 2;
+ final int nentries = c.data.length / 2;
hist = new int[nentries];
for (int i = 0; i < hist.length; i++) {
- hist[i] = PngHelper.readInt2fromBytes(c.data, i * 2);
+ hist[i] = PngHelperInternal.readInt2fromBytes(c.data, i * 2);
}
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
if (!imgInfo.indexed)
throw new PngjException("only indexed images accept a HIST chunk");
ChunkRaw c = null;
c = createEmptyChunk(hist.length * 2, true);
for (int i = 0; i < hist.length; i++) {
- PngHelper.writeInt2tobytes(hist[i], c.data, i * 2);
+ PngHelperInternal.writeInt2tobytes(hist[i], c.data, i * 2);
}
return c;
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkHIST otherx = (PngChunkHIST) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkHIST otherx = (PngChunkHIST) other;
hist = new int[otherx.hist.length];
System.arraycopy(otherx.hist, 0, hist, 0, otherx.hist.length);
}
@@ -60,7 +58,7 @@ public class PngChunkHIST extends PngChunk {
return hist;
}
- public void setHist(int[] hist) {
+ public void setHist(final int[] hist) {
this.hist = hist;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java
index db1c1ba64..17f69861c 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java
@@ -1,32 +1,33 @@
package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
+import jogamp.opengl.util.pngj.PngjException;
-/*
+/**
+ * iCCP chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11iCCP
*/
-public class PngChunkICCP extends PngChunk {
+public class PngChunkICCP extends PngChunkSingle {
+ public final static String ID = ChunkHelper.iCCP;
+
// http://www.w3.org/TR/PNG/#11iCCP
private String profileName;
private byte[] compressedProfile; // copmression/decopmresion is done in getter/setter
- public PngChunkICCP(ImageInfo info) {
- super(ChunkHelper.iCCP, info);
- }
-
- @Override
- public boolean mustGoBeforeIDAT() {
- return true;
+ public PngChunkICCP(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public boolean mustGoBeforePLTE() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_PLTE_AND_IDAT;
}
@Override
- public ChunkRaw createChunk() {
- ChunkRaw c = createEmptyChunk(profileName.length() + compressedProfile.length + 2, true);
+ public ChunkRaw createRawChunk() {
+ final ChunkRaw c = createEmptyChunk(profileName.length() + compressedProfile.length + 2, true);
System.arraycopy(ChunkHelper.toBytes(profileName), 0, c.data, 0, profileName.length());
c.data[profileName.length()] = 0;
c.data[profileName.length() + 1] = 0;
@@ -35,20 +36,20 @@ public class PngChunkICCP extends PngChunk {
}
@Override
- public void parseFromChunk(ChunkRaw chunk) {
- int pos0 = ChunkHelper.posNullByte(chunk.data);
- profileName = new String(chunk.data, 0, pos0, PngHelper.charsetLatin1);
- int comp = (chunk.data[pos0 + 1] & 0xff);
+ public void parseFromRaw(final ChunkRaw chunk) {
+ final int pos0 = ChunkHelper.posNullByte(chunk.data);
+ profileName = new String(chunk.data, 0, pos0, PngHelperInternal.charsetLatin1);
+ final int comp = (chunk.data[pos0 + 1] & 0xff);
if (comp != 0)
- throw new RuntimeException("bad compression for ChunkTypeICCP");
- int compdatasize = chunk.data.length - (pos0 + 2);
+ throw new PngjException("bad compression for ChunkTypeICCP");
+ final int compdatasize = chunk.data.length - (pos0 + 2);
compressedProfile = new byte[compdatasize];
System.arraycopy(chunk.data, pos0 + 2, compressedProfile, 0, compdatasize);
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkICCP otherx = (PngChunkICCP) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkICCP otherx = (PngChunkICCP) other;
profileName = otherx.profileName;
compressedProfile = new byte[otherx.compressedProfile.length];
System.arraycopy(otherx.compressedProfile, 0, compressedProfile, 0, otherx.compressedProfile.length); // deep
@@ -58,13 +59,13 @@ public class PngChunkICCP extends PngChunk {
/**
* The profile should be uncompressed bytes
*/
- public void setProfileNameAndContent(String name, byte[] profile) {
+ public void setProfileNameAndContent(final String name, final byte[] profile) {
profileName = name;
compressedProfile = ChunkHelper.compressBytes(profile, true);
}
- public void setProfileNameAndContent(String name, String profile) {
- setProfileNameAndContent(name, profile.getBytes(PngHelper.charsetLatin1));
+ public void setProfileNameAndContent(final String name, final String profile) {
+ setProfileNameAndContent(name, profile.getBytes(PngHelperInternal.charsetLatin1));
}
public String getProfileName() {
@@ -79,7 +80,7 @@ public class PngChunkICCP extends PngChunk {
}
public String getProfileAsString() {
- return new String(getProfile(), PngHelper.charsetLatin1);
+ return new String(getProfile(), PngHelperInternal.charsetLatin1);
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java
index a7cb95dbf..f7bee4b11 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java
@@ -2,24 +2,39 @@ package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-public class PngChunkIDAT extends PngChunk {
+/**
+ * IDAT chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11IDAT
+ * <p>
+ * This is dummy placeholder - we write/read this chunk (actually several) by
+ * special code.
+ */
+public class PngChunkIDAT extends PngChunkMultiple {
+ public final static String ID = ChunkHelper.IDAT;
+
// http://www.w3.org/TR/PNG/#11IDAT
- // This is dummy placeholder - we write/read this chunk (actually several)
- // by special code.
- public PngChunkIDAT(ImageInfo i) {
- super(ChunkHelper.IDAT, i);
+ public PngChunkIDAT(final ImageInfo i, final int len, final long offset) {
+ super(ID, i);
+ this.length = len;
+ this.offset = offset;
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NA;
}
@Override
- public ChunkRaw createChunk() {// does nothing
+ public ChunkRaw createRawChunk() {// does nothing
return null;
}
@Override
- public void parseFromChunk(ChunkRaw c) { // does nothing
+ public void parseFromRaw(final ChunkRaw c) { // does nothing
}
@Override
- public void cloneDataFromRead(PngChunk other) {
+ public void cloneDataFromRead(final PngChunk other) {
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java
index 0d5b266da..3f6e47b09 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java
@@ -2,25 +2,37 @@ package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-public class PngChunkIEND extends PngChunk {
+/**
+ * IEND chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11IEND
+ */
+public class PngChunkIEND extends PngChunkSingle {
+ public final static String ID = ChunkHelper.IEND;
+
// http://www.w3.org/TR/PNG/#11IEND
// this is a dummy placeholder
- public PngChunkIEND(ImageInfo info) {
- super(ChunkHelper.IEND, info);
+ public PngChunkIEND(final ImageInfo info) {
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NA;
}
@Override
- public ChunkRaw createChunk() {
- ChunkRaw c = new ChunkRaw(0, ChunkHelper.b_IEND, false);
+ public ChunkRaw createRawChunk() {
+ final ChunkRaw c = new ChunkRaw(0, ChunkHelper.b_IEND, false);
return c;
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
// this is not used
}
@Override
- public void cloneDataFromRead(PngChunk other) {
+ public void cloneDataFromRead(final PngChunk other) {
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIHDR.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIHDR.java
index fcb4150ff..71e0ec90e 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIHDR.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIHDR.java
@@ -3,14 +3,20 @@ package jogamp.opengl.util.pngj.chunks;
import java.io.ByteArrayInputStream;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
/**
- * this is a special chunk!
+ * IHDR chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11IHDR
+ * <p>
+ * This is a special critical Chunk.
*/
-public class PngChunkIHDR extends PngChunk {
+public class PngChunkIHDR extends PngChunkSingle {
+ public final static String ID = ChunkHelper.IHDR;
+
private int cols;
private int rows;
private int bitspc;
@@ -21,17 +27,22 @@ public class PngChunkIHDR extends PngChunk {
// http://www.w3.org/TR/PNG/#11IHDR
//
- public PngChunkIHDR(ImageInfo info) {
- super(ChunkHelper.IHDR, info);
+ public PngChunkIHDR(final ImageInfo info) {
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NA;
}
@Override
- public ChunkRaw createChunk() {
- ChunkRaw c = new ChunkRaw(13, ChunkHelper.b_IHDR, true);
+ public ChunkRaw createRawChunk() {
+ final ChunkRaw c = new ChunkRaw(13, ChunkHelper.b_IHDR, true);
int offset = 0;
- PngHelper.writeInt4tobytes(cols, c.data, offset);
+ PngHelperInternal.writeInt4tobytes(cols, c.data, offset);
offset += 4;
- PngHelper.writeInt4tobytes(rows, c.data, offset);
+ PngHelperInternal.writeInt4tobytes(rows, c.data, offset);
offset += 4;
c.data[offset++] = (byte) bitspc;
c.data[offset++] = (byte) colormodel;
@@ -42,23 +53,23 @@ public class PngChunkIHDR extends PngChunk {
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
if (c.len != 13)
throw new PngjException("Bad IDHR len " + c.len);
- ByteArrayInputStream st = c.getAsByteStream();
- cols = PngHelper.readInt4(st);
- rows = PngHelper.readInt4(st);
+ final ByteArrayInputStream st = c.getAsByteStream();
+ cols = PngHelperInternal.readInt4(st);
+ rows = PngHelperInternal.readInt4(st);
// bit depth: number of bits per channel
- bitspc = PngHelper.readByte(st);
- colormodel = PngHelper.readByte(st);
- compmeth = PngHelper.readByte(st);
- filmeth = PngHelper.readByte(st);
- interlaced = PngHelper.readByte(st);
+ bitspc = PngHelperInternal.readByte(st);
+ colormodel = PngHelperInternal.readByte(st);
+ compmeth = PngHelperInternal.readByte(st);
+ filmeth = PngHelperInternal.readByte(st);
+ interlaced = PngHelperInternal.readByte(st);
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkIHDR otherx = (PngChunkIHDR) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkIHDR otherx = (PngChunkIHDR) other;
cols = otherx.cols;
rows = otherx.rows;
bitspc = otherx.bitspc;
@@ -72,7 +83,7 @@ public class PngChunkIHDR extends PngChunk {
return cols;
}
- public void setCols(int cols) {
+ public void setCols(final int cols) {
this.cols = cols;
}
@@ -80,7 +91,7 @@ public class PngChunkIHDR extends PngChunk {
return rows;
}
- public void setRows(int rows) {
+ public void setRows(final int rows) {
this.rows = rows;
}
@@ -88,7 +99,7 @@ public class PngChunkIHDR extends PngChunk {
return bitspc;
}
- public void setBitspc(int bitspc) {
+ public void setBitspc(final int bitspc) {
this.bitspc = bitspc;
}
@@ -96,7 +107,7 @@ public class PngChunkIHDR extends PngChunk {
return colormodel;
}
- public void setColormodel(int colormodel) {
+ public void setColormodel(final int colormodel) {
this.colormodel = colormodel;
}
@@ -104,7 +115,7 @@ public class PngChunkIHDR extends PngChunk {
return compmeth;
}
- public void setCompmeth(int compmeth) {
+ public void setCompmeth(final int compmeth) {
this.compmeth = compmeth;
}
@@ -112,7 +123,7 @@ public class PngChunkIHDR extends PngChunk {
return filmeth;
}
- public void setFilmeth(int filmeth) {
+ public void setFilmeth(final int filmeth) {
this.filmeth = filmeth;
}
@@ -120,7 +131,7 @@ public class PngChunkIHDR extends PngChunk {
return interlaced;
}
- public void setInterlaced(int interlaced) {
+ public void setInterlaced(final int interlaced) {
this.interlaced = interlaced;
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java
index 4e5c7c74a..738f92471 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java
@@ -4,56 +4,59 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
/**
- * UNTESTED!
+ * iTXt chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11iTXt
*/
public class PngChunkITXT extends PngChunkTextVar {
+ public final static String ID = ChunkHelper.iTXt;
private boolean compressed = false;
private String langTag = "";
private String translatedTag = "";
// http://www.w3.org/TR/PNG/#11iTXt
- public PngChunkITXT(ImageInfo info) {
- super(ChunkHelper.iTXt, info);
+ public PngChunkITXT(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public ChunkRaw createChunk() {
- if (val.isEmpty() || key.isEmpty())
- return null;
+ public ChunkRaw createRawChunk() {
+ if (key.isEmpty())
+ throw new PngjException("Text chunk key must be non empty");
try {
- ByteArrayOutputStream ba = new ByteArrayOutputStream();
- ba.write(key.getBytes(PngHelper.charsetLatin1));
+ final ByteArrayOutputStream ba = new ByteArrayOutputStream();
+ ba.write(ChunkHelper.toBytes(key));
ba.write(0); // separator
ba.write(compressed ? 1 : 0);
ba.write(0); // compression method (always 0)
- ba.write(langTag.getBytes(PngHelper.charsetUTF8));
+ ba.write(ChunkHelper.toBytes(langTag));
ba.write(0); // separator
- ba.write(translatedTag.getBytes(PngHelper.charsetUTF8));
+ ba.write(ChunkHelper.toBytesUTF8(translatedTag));
ba.write(0); // separator
- byte[] textbytes = val.getBytes(PngHelper.charsetUTF8);
+ byte[] textbytes = ChunkHelper.toBytesUTF8(val);
if (compressed) {
textbytes = ChunkHelper.compressBytes(textbytes, true);
}
ba.write(textbytes);
- byte[] b = ba.toByteArray();
- ChunkRaw chunk = createEmptyChunk(b.length, false);
+ final byte[] b = ba.toByteArray();
+ final ChunkRaw chunk = createEmptyChunk(b.length, false);
chunk.data = b;
return chunk;
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new PngjException(e);
}
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
int nullsFound = 0;
- int[] nullsIdx = new int[3];
+ final int[] nullsIdx = new int[3];
for (int i = 0; i < c.data.length; i++) {
if (c.data[i] != 0)
continue;
@@ -66,26 +69,27 @@ public class PngChunkITXT extends PngChunkTextVar {
}
if (nullsFound != 3)
throw new PngjException("Bad formed PngChunkITXT chunk");
- key = new String(c.data, 0, nullsIdx[0], PngHelper.charsetLatin1);
+ key = ChunkHelper.toString(c.data, 0, nullsIdx[0]);
int i = nullsIdx[0] + 1;
compressed = c.data[i] == 0 ? false : true;
i++;
if (compressed && c.data[i] != 0)
throw new PngjException("Bad formed PngChunkITXT chunk - bad compression method ");
- langTag = new String(c.data, i, nullsIdx[1] - i, PngHelper.charsetLatin1);
- translatedTag = new String(c.data, nullsIdx[1] + 1, nullsIdx[2] - nullsIdx[1] - 1, PngHelper.charsetUTF8);
+ langTag = new String(c.data, i, nullsIdx[1] - i, PngHelperInternal.charsetLatin1);
+ translatedTag = new String(c.data, nullsIdx[1] + 1, nullsIdx[2] - nullsIdx[1] - 1,
+ PngHelperInternal.charsetUTF8);
i = nullsIdx[2] + 1;
if (compressed) {
- byte[] bytes = ChunkHelper.compressBytes(c.data, i, c.data.length - i, false);
- val = new String(bytes, PngHelper.charsetUTF8);
+ final byte[] bytes = ChunkHelper.compressBytes(c.data, i, c.data.length - i, false);
+ val = ChunkHelper.toStringUTF8(bytes);
} else {
- val = new String(c.data, i, c.data.length - i, PngHelper.charsetUTF8);
+ val = ChunkHelper.toStringUTF8(c.data, i, c.data.length - i);
}
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkITXT otherx = (PngChunkITXT) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkITXT otherx = (PngChunkITXT) other;
key = otherx.key;
val = otherx.val;
compressed = otherx.compressed;
@@ -97,7 +101,7 @@ public class PngChunkITXT extends PngChunkTextVar {
return compressed;
}
- public void setCompressed(boolean compressed) {
+ public void setCompressed(final boolean compressed) {
this.compressed = compressed;
}
@@ -105,7 +109,7 @@ public class PngChunkITXT extends PngChunkTextVar {
return langTag;
}
- public void setLangtag(String langtag) {
+ public void setLangtag(final String langtag) {
this.langTag = langtag;
}
@@ -113,7 +117,7 @@ public class PngChunkITXT extends PngChunkTextVar {
return translatedTag;
}
- public void setTranslatedTag(String translatedTag) {
+ public void setTranslatedTag(final String translatedTag) {
this.translatedTag = translatedTag;
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkMultiple.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkMultiple.java
new file mode 100644
index 000000000..5a7bee98c
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkMultiple.java
@@ -0,0 +1,28 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+
+/**
+ * PNG chunk type (abstract) that allows multiple instances in same image.
+ */
+public abstract class PngChunkMultiple extends PngChunk {
+
+ protected PngChunkMultiple(final String id, final ImageInfo imgInfo) {
+ super(id, imgInfo);
+ }
+
+ @Override
+ public final boolean allowsMultiple() {
+ return true;
+ }
+
+ /**
+ * NOTE: this chunk uses the default Object's equals() hashCode()
+ * implementation.
+ *
+ * This is the right thing to do, normally.
+ *
+ * This is important, eg see ChunkList.removeFromList()
+ */
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkOFFS.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkOFFS.java
new file mode 100644
index 000000000..2217a59b4
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkOFFS.java
@@ -0,0 +1,89 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelperInternal;
+import jogamp.opengl.util.pngj.PngjException;
+
+/**
+ * oFFs chunk.
+ * <p>
+ * see http://www.libpng.org/pub/png/spec/register/pngext-1.3.0-pdg.html#C.oFFs
+ */
+public class PngChunkOFFS extends PngChunkSingle {
+ public final static String ID = "oFFs";
+
+ // http://www.libpng.org/pub/png/spec/register/pngext-1.3.0-pdg.html#C.oFFs
+ private long posX;
+ private long posY;
+ private int units; // 0: pixel 1:micrometer
+
+ public PngChunkOFFS(final ImageInfo info) {
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_IDAT;
+ }
+
+ @Override
+ public ChunkRaw createRawChunk() {
+ final ChunkRaw c = createEmptyChunk(9, true);
+ PngHelperInternal.writeInt4tobytes((int) posX, c.data, 0);
+ PngHelperInternal.writeInt4tobytes((int) posY, c.data, 4);
+ c.data[8] = (byte) units;
+ return c;
+ }
+
+ @Override
+ public void parseFromRaw(final ChunkRaw chunk) {
+ if (chunk.len != 9)
+ throw new PngjException("bad chunk length " + chunk);
+ posX = PngHelperInternal.readInt4fromBytes(chunk.data, 0);
+ if (posX < 0)
+ posX += 0x100000000L;
+ posY = PngHelperInternal.readInt4fromBytes(chunk.data, 4);
+ if (posY < 0)
+ posY += 0x100000000L;
+ units = PngHelperInternal.readInt1fromByte(chunk.data, 8);
+ }
+
+ @Override
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkOFFS otherx = (PngChunkOFFS) other;
+ this.posX = otherx.posX;
+ this.posY = otherx.posY;
+ this.units = otherx.units;
+ }
+
+ /**
+ * 0: pixel, 1:micrometer
+ */
+ public int getUnits() {
+ return units;
+ }
+
+ /**
+ * 0: pixel, 1:micrometer
+ */
+ public void setUnits(final int units) {
+ this.units = units;
+ }
+
+ public long getPosX() {
+ return posX;
+ }
+
+ public void setPosX(final long posX) {
+ this.posX = posX;
+ }
+
+ public long getPosY() {
+ return posY;
+ }
+
+ public void setPosY(final long posY) {
+ this.posY = posY;
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java
index 47e2c492c..fc647273e 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java
@@ -1,49 +1,55 @@
package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
+/**
+ * pHYs chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11pHYs
+ */
+public class PngChunkPHYS extends PngChunkSingle {
+ public final static String ID = ChunkHelper.pHYs;
-public class PngChunkPHYS extends PngChunk {
// http://www.w3.org/TR/PNG/#11pHYs
private long pixelsxUnitX;
private long pixelsxUnitY;
private int units; // 0: unknown 1:metre
- public PngChunkPHYS(ImageInfo info) {
- super(ChunkHelper.pHYs, info);
+ public PngChunkPHYS(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public boolean mustGoBeforeIDAT() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_IDAT;
}
@Override
- public ChunkRaw createChunk() {
- ChunkRaw c = createEmptyChunk(9, true);
- PngHelper.writeInt4tobytes((int) pixelsxUnitX, c.data, 0);
- PngHelper.writeInt4tobytes((int) pixelsxUnitY, c.data, 4);
+ public ChunkRaw createRawChunk() {
+ final ChunkRaw c = createEmptyChunk(9, true);
+ PngHelperInternal.writeInt4tobytes((int) pixelsxUnitX, c.data, 0);
+ PngHelperInternal.writeInt4tobytes((int) pixelsxUnitY, c.data, 4);
c.data[8] = (byte) units;
return c;
}
@Override
- public void parseFromChunk(ChunkRaw chunk) {
+ public void parseFromRaw(final ChunkRaw chunk) {
if (chunk.len != 9)
throw new PngjException("bad chunk length " + chunk);
- pixelsxUnitX = PngHelper.readInt4fromBytes(chunk.data, 0);
+ pixelsxUnitX = PngHelperInternal.readInt4fromBytes(chunk.data, 0);
if (pixelsxUnitX < 0)
pixelsxUnitX += 0x100000000L;
- pixelsxUnitY = PngHelper.readInt4fromBytes(chunk.data, 4);
+ pixelsxUnitY = PngHelperInternal.readInt4fromBytes(chunk.data, 4);
if (pixelsxUnitY < 0)
pixelsxUnitY += 0x100000000L;
- units = PngHelper.readInt1fromByte(chunk.data, 8);
+ units = PngHelperInternal.readInt1fromByte(chunk.data, 8);
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkPHYS otherx = (PngChunkPHYS) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkPHYS otherx = (PngChunkPHYS) other;
this.pixelsxUnitX = otherx.pixelsxUnitX;
this.pixelsxUnitY = otherx.pixelsxUnitY;
this.units = otherx.units;
@@ -53,7 +59,7 @@ public class PngChunkPHYS extends PngChunk {
return pixelsxUnitX;
}
- public void setPixelsxUnitX(long pixelsxUnitX) {
+ public void setPixelsxUnitX(final long pixelsxUnitX) {
this.pixelsxUnitX = pixelsxUnitX;
}
@@ -61,7 +67,7 @@ public class PngChunkPHYS extends PngChunk {
return pixelsxUnitY;
}
- public void setPixelsxUnitY(long pixelsxUnitY) {
+ public void setPixelsxUnitY(final long pixelsxUnitY) {
this.pixelsxUnitY = pixelsxUnitY;
}
@@ -69,7 +75,7 @@ public class PngChunkPHYS extends PngChunk {
return units;
}
- public void setUnits(int units) {
+ public void setUnits(final int units) {
this.units = units;
}
@@ -81,7 +87,7 @@ public class PngChunkPHYS extends PngChunk {
public double getAsDpi() {
if (units != 1 || pixelsxUnitX != pixelsxUnitY)
return -1;
- return ((double) pixelsxUnitX) * 0.0254;
+ return (pixelsxUnitX) * 0.0254;
}
/**
@@ -90,16 +96,16 @@ public class PngChunkPHYS extends PngChunk {
public double[] getAsDpi2() {
if (units != 1)
return new double[] { -1, -1 };
- return new double[] { ((double) pixelsxUnitX) * 0.0254, ((double) pixelsxUnitY) * 0.0254 };
+ return new double[] { (pixelsxUnitX) * 0.0254, (pixelsxUnitY) * 0.0254 };
}
- public void setAsDpi(double dpi) {
+ public void setAsDpi(final double dpi) {
units = 1;
pixelsxUnitX = (long) (dpi / 0.0254 + 0.5);
pixelsxUnitY = pixelsxUnitX;
}
- public void setAsDpi2(double dpix, double dpiy) {
+ public void setAsDpi2(final double dpix, final double dpiy) {
units = 1;
pixelsxUnitX = (long) (dpix / 0.0254 + 0.5);
pixelsxUnitY = (long) (dpiy / 0.0254 + 0.5);
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPLTE.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPLTE.java
index 123080bb3..0f135d1ed 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPLTE.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPLTE.java
@@ -3,10 +3,16 @@ package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
import jogamp.opengl.util.pngj.PngjException;
-/*
- * Palette chunk *this is critical*
+/**
+ * PLTE chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11PLTE
+ * <p>
+ * Critical chunk
*/
-public class PngChunkPLTE extends PngChunk {
+public class PngChunkPLTE extends PngChunkSingle {
+ public final static String ID = ChunkHelper.PLTE;
+
// http://www.w3.org/TR/PNG/#11PLTE
private int nentries = 0;
/**
@@ -14,15 +20,20 @@ public class PngChunkPLTE extends PngChunk {
*/
private int[] entries;
- public PngChunkPLTE(ImageInfo info) {
- super(ChunkHelper.PLTE, info);
+ public PngChunkPLTE(final ImageInfo info) {
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NA;
}
@Override
- public ChunkRaw createChunk() {
- int len = 3 * nentries;
- int[] rgb = new int[3];
- ChunkRaw c = createEmptyChunk(len, true);
+ public ChunkRaw createRawChunk() {
+ final int len = 3 * nentries;
+ final int[] rgb = new int[3];
+ final ChunkRaw c = createEmptyChunk(len, true);
for (int n = 0, i = 0; n < nentries; n++) {
getEntryRgb(n, rgb);
c.data[i++] = (byte) rgb[0];
@@ -33,21 +44,21 @@ public class PngChunkPLTE extends PngChunk {
}
@Override
- public void parseFromChunk(ChunkRaw chunk) {
+ public void parseFromRaw(final ChunkRaw chunk) {
setNentries(chunk.len / 3);
for (int n = 0, i = 0; n < nentries; n++) {
- setEntry(n, (int) (chunk.data[i++] & 0xff), (int) (chunk.data[i++] & 0xff), (int) (chunk.data[i++] & 0xff));
+ setEntry(n, chunk.data[i++] & 0xff, chunk.data[i++] & 0xff, chunk.data[i++] & 0xff);
}
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkPLTE otherx = (PngChunkPLTE) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkPLTE otherx = (PngChunkPLTE) other;
this.setNentries(otherx.getNentries());
System.arraycopy(otherx.entries, 0, entries, 0, nentries);
}
- public void setNentries(int n) {
+ public void setNentries(final int n) {
nentries = n;
if (nentries < 1 || nentries > 256)
throw new PngjException("invalid pallette - nentries=" + nentries);
@@ -60,20 +71,20 @@ public class PngChunkPLTE extends PngChunk {
return nentries;
}
- public void setEntry(int n, int r, int g, int b) {
+ public void setEntry(final int n, final int r, final int g, final int b) {
entries[n] = ((r << 16) | (g << 8) | b);
}
- public int getEntry(int n) {
+ public int getEntry(final int n) {
return entries[n];
}
- public void getEntryRgb(int n, int[] rgb) {
+ public void getEntryRgb(final int n, final int[] rgb) {
getEntryRgb(n, rgb, 0);
}
- public void getEntryRgb(int n, int[] rgb, int offset) {
- int v = entries[n];
+ public void getEntryRgb(final int n, final int[] rgb, final int offset) {
+ final int v = entries[n];
rgb[offset + 0] = ((v & 0xff0000) >> 16);
rgb[offset + 1] = ((v & 0xff00) >> 8);
rgb[offset + 2] = (v & 0xff);
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSBIT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSBIT.java
index 6850d260d..53858da83 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSBIT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSBIT.java
@@ -1,31 +1,31 @@
package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
-/*
+/**
+ * sBIT chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11sBIT
+ * <p>
+ * this chunk structure depends on the image type
*/
-public class PngChunkSBIT extends PngChunk {
+public class PngChunkSBIT extends PngChunkSingle {
+ public final static String ID = ChunkHelper.sBIT;
// http://www.w3.org/TR/PNG/#11sBIT
- // this chunk structure depends on the image type
// significant bits
private int graysb, alphasb;
private int redsb, greensb, bluesb;
- public PngChunkSBIT(ImageInfo info) {
- super(ChunkHelper.sBIT, info);
+ public PngChunkSBIT(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public boolean mustGoBeforeIDAT() {
- return true;
- }
-
- @Override
- public boolean mustGoBeforePLTE() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_PLTE_AND_IDAT;
}
private int getLen() {
@@ -36,24 +36,24 @@ public class PngChunkSBIT extends PngChunk {
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
if (c.len != getLen())
throw new PngjException("bad chunk length " + c);
if (imgInfo.greyscale) {
- graysb = PngHelper.readInt1fromByte(c.data, 0);
+ graysb = PngHelperInternal.readInt1fromByte(c.data, 0);
if (imgInfo.alpha)
- alphasb = PngHelper.readInt1fromByte(c.data, 1);
+ alphasb = PngHelperInternal.readInt1fromByte(c.data, 1);
} else {
- redsb = PngHelper.readInt1fromByte(c.data, 0);
- greensb = PngHelper.readInt1fromByte(c.data, 1);
- bluesb = PngHelper.readInt1fromByte(c.data, 2);
+ redsb = PngHelperInternal.readInt1fromByte(c.data, 0);
+ greensb = PngHelperInternal.readInt1fromByte(c.data, 1);
+ bluesb = PngHelperInternal.readInt1fromByte(c.data, 2);
if (imgInfo.alpha)
- alphasb = PngHelper.readInt1fromByte(c.data, 3);
+ alphasb = PngHelperInternal.readInt1fromByte(c.data, 3);
}
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
ChunkRaw c = null;
c = createEmptyChunk(getLen(), true);
if (imgInfo.greyscale) {
@@ -71,8 +71,8 @@ public class PngChunkSBIT extends PngChunk {
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkSBIT otherx = (PngChunkSBIT) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkSBIT otherx = (PngChunkSBIT) other;
graysb = otherx.graysb;
redsb = otherx.redsb;
greensb = otherx.greensb;
@@ -80,7 +80,7 @@ public class PngChunkSBIT extends PngChunk {
alphasb = otherx.alphasb;
}
- public void setGraysb(int gray) {
+ public void setGraysb(final int gray) {
if (!imgInfo.greyscale)
throw new PngjException("only greyscale images support this");
graysb = gray;
@@ -92,7 +92,7 @@ public class PngChunkSBIT extends PngChunk {
return graysb;
}
- public void setAlphasb(int a) {
+ public void setAlphasb(final int a) {
if (!imgInfo.alpha)
throw new PngjException("only images with alpha support this");
alphasb = a;
@@ -106,9 +106,9 @@ public class PngChunkSBIT extends PngChunk {
/**
* Set rgb values
- *
+ *
*/
- public void setRGB(int r, int g, int b) {
+ public void setRGB(final int r, final int g, final int b) {
if (imgInfo.greyscale || imgInfo.indexed)
throw new PngjException("only rgb or rgba images support this");
redsb = r;
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java
index 953adb7d9..44ff249ac 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java
@@ -4,59 +4,60 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
+/**
+ * sPLT chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11sPLT
+ */
+public class PngChunkSPLT extends PngChunkMultiple {
+ public final static String ID = ChunkHelper.sPLT;
-public class PngChunkSPLT extends PngChunk {
// http://www.w3.org/TR/PNG/#11sPLT
private String palName;
private int sampledepth; // 8/16
private int[] palette; // 5 elements per entry
- public PngChunkSPLT(ImageInfo info) {
- super(ChunkHelper.sPLT, info);
+ public PngChunkSPLT(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public boolean allowsMultiple() {
- return true; // allows multiple, but pallete name should be different
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_IDAT;
}
@Override
- public boolean mustGoBeforeIDAT() {
- return true;
- }
-
- @Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
try {
- ByteArrayOutputStream ba = new ByteArrayOutputStream();
- ba.write(palName.getBytes(PngHelper.charsetLatin1));
+ final ByteArrayOutputStream ba = new ByteArrayOutputStream();
+ ba.write(palName.getBytes(PngHelperInternal.charsetLatin1));
ba.write(0); // separator
ba.write((byte) sampledepth);
- int nentries = getNentries();
+ final int nentries = getNentries();
for (int n = 0; n < nentries; n++) {
for (int i = 0; i < 4; i++) {
if (sampledepth == 8)
- PngHelper.writeByte(ba, (byte) palette[n * 5 + i]);
+ PngHelperInternal.writeByte(ba, (byte) palette[n * 5 + i]);
else
- PngHelper.writeInt2(ba, palette[n * 5 + i]);
+ PngHelperInternal.writeInt2(ba, palette[n * 5 + i]);
}
- PngHelper.writeInt2(ba, palette[n * 5 + 4]);
+ PngHelperInternal.writeInt2(ba, palette[n * 5 + 4]);
}
- byte[] b = ba.toByteArray();
- ChunkRaw chunk = createEmptyChunk(b.length, false);
+ final byte[] b = ba.toByteArray();
+ final ChunkRaw chunk = createEmptyChunk(b.length, false);
chunk.data = b;
return chunk;
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new PngjException(e);
}
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
int t = -1;
for (int i = 0; i < c.data.length; i++) { // look for first zero
if (c.data[i] == 0) {
@@ -66,30 +67,30 @@ public class PngChunkSPLT extends PngChunk {
}
if (t <= 0 || t > c.data.length - 2)
throw new PngjException("bad sPLT chunk: no separator found");
- palName = new String(c.data, 0, t, PngHelper.charsetLatin1);
- sampledepth = PngHelper.readInt1fromByte(c.data, t + 1);
+ palName = new String(c.data, 0, t, PngHelperInternal.charsetLatin1);
+ sampledepth = PngHelperInternal.readInt1fromByte(c.data, t + 1);
t += 2;
- int nentries = (c.data.length - t) / (sampledepth == 8 ? 6 : 10);
+ final int nentries = (c.data.length - t) / (sampledepth == 8 ? 6 : 10);
palette = new int[nentries * 5];
int r, g, b, a, f, ne;
ne = 0;
for (int i = 0; i < nentries; i++) {
if (sampledepth == 8) {
- r = PngHelper.readInt1fromByte(c.data, t++);
- g = PngHelper.readInt1fromByte(c.data, t++);
- b = PngHelper.readInt1fromByte(c.data, t++);
- a = PngHelper.readInt1fromByte(c.data, t++);
+ r = PngHelperInternal.readInt1fromByte(c.data, t++);
+ g = PngHelperInternal.readInt1fromByte(c.data, t++);
+ b = PngHelperInternal.readInt1fromByte(c.data, t++);
+ a = PngHelperInternal.readInt1fromByte(c.data, t++);
} else {
- r = PngHelper.readInt2fromBytes(c.data, t);
+ r = PngHelperInternal.readInt2fromBytes(c.data, t);
t += 2;
- g = PngHelper.readInt2fromBytes(c.data, t);
+ g = PngHelperInternal.readInt2fromBytes(c.data, t);
t += 2;
- b = PngHelper.readInt2fromBytes(c.data, t);
+ b = PngHelperInternal.readInt2fromBytes(c.data, t);
t += 2;
- a = PngHelper.readInt2fromBytes(c.data, t);
+ a = PngHelperInternal.readInt2fromBytes(c.data, t);
t += 2;
}
- f = PngHelper.readInt2fromBytes(c.data, t);
+ f = PngHelperInternal.readInt2fromBytes(c.data, t);
t += 2;
palette[ne++] = r;
palette[ne++] = g;
@@ -100,8 +101,8 @@ public class PngChunkSPLT extends PngChunk {
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkSPLT otherx = (PngChunkSPLT) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkSPLT otherx = (PngChunkSPLT) other;
palName = otherx.palName;
sampledepth = otherx.sampledepth;
palette = new int[otherx.palette.length];
@@ -116,7 +117,7 @@ public class PngChunkSPLT extends PngChunk {
return palName;
}
- public void setPalName(String palName) {
+ public void setPalName(final String palName) {
this.palName = palName;
}
@@ -124,7 +125,7 @@ public class PngChunkSPLT extends PngChunk {
return sampledepth;
}
- public void setSampledepth(int sampledepth) {
+ public void setSampledepth(final int sampledepth) {
this.sampledepth = sampledepth;
}
@@ -132,7 +133,7 @@ public class PngChunkSPLT extends PngChunk {
return palette;
}
- public void setPalette(int[] palette) {
+ public void setPalette(final int[] palette) {
this.palette = palette;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSRGB.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSRGB.java
index 774558785..19504b917 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSRGB.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSRGB.java
@@ -1,12 +1,17 @@
package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
-/*
+/**
+ * sRGB chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11sRGB
*/
-public class PngChunkSRGB extends PngChunk {
+public class PngChunkSRGB extends PngChunkSingle {
+ public final static String ID = ChunkHelper.sRGB;
+
// http://www.w3.org/TR/PNG/#11sRGB
public static final int RENDER_INTENT_Perceptual = 0;
@@ -16,29 +21,24 @@ public class PngChunkSRGB extends PngChunk {
private int intent;
- public PngChunkSRGB(ImageInfo info) {
- super(ChunkHelper.sRGB, info);
- }
-
- @Override
- public boolean mustGoBeforeIDAT() {
- return true;
+ public PngChunkSRGB(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public boolean mustGoBeforePLTE() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_PLTE_AND_IDAT;
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
if (c.len != 1)
throw new PngjException("bad chunk length " + c);
- intent = PngHelper.readInt1fromByte(c.data, 0);
+ intent = PngHelperInternal.readInt1fromByte(c.data, 0);
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
ChunkRaw c = null;
c = createEmptyChunk(1, true);
c.data[0] = (byte) intent;
@@ -46,8 +46,8 @@ public class PngChunkSRGB extends PngChunk {
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkSRGB otherx = (PngChunkSRGB) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkSRGB otherx = (PngChunkSRGB) other;
intent = otherx.intent;
}
@@ -55,7 +55,7 @@ public class PngChunkSRGB extends PngChunk {
return intent;
}
- public void setIntent(int intent) {
+ public void setIntent(final int intent) {
this.intent = intent;
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSTER.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSTER.java
new file mode 100644
index 000000000..52037b396
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSTER.java
@@ -0,0 +1,60 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngjException;
+
+/**
+ * sTER chunk.
+ * <p>
+ * see http://www.libpng.org/pub/png/spec/register/pngext-1.3.0-pdg.html#C.sTER
+ */
+public class PngChunkSTER extends PngChunkSingle {
+ public final static String ID = "sTER";
+
+ // http://www.libpng.org/pub/png/spec/register/pngext-1.3.0-pdg.html#C.sTER
+ private byte mode; // 0: cross-fuse layout 1: diverging-fuse layout
+
+ public PngChunkSTER(final ImageInfo info) {
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_IDAT;
+ }
+
+ @Override
+ public ChunkRaw createRawChunk() {
+ final ChunkRaw c = createEmptyChunk(1, true);
+ c.data[0] = mode;
+ return c;
+ }
+
+ @Override
+ public void parseFromRaw(final ChunkRaw chunk) {
+ if (chunk.len != 1)
+ throw new PngjException("bad chunk length " + chunk);
+ mode = chunk.data[0];
+ }
+
+ @Override
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkSTER otherx = (PngChunkSTER) other;
+ this.mode = otherx.mode;
+ }
+
+ /**
+ * 0: cross-fuse layout 1: diverging-fuse layout
+ */
+ public byte getMode() {
+ return mode;
+ }
+
+ /**
+ * 0: cross-fuse layout 1: diverging-fuse layout
+ */
+ public void setMode(final byte mode) {
+ this.mode = mode;
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSingle.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSingle.java
new file mode 100644
index 000000000..c5e9407b1
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSingle.java
@@ -0,0 +1,45 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+
+/**
+ * PNG chunk type (abstract) that does not allow multiple instances in same
+ * image.
+ */
+public abstract class PngChunkSingle extends PngChunk {
+
+ protected PngChunkSingle(final String id, final ImageInfo imgInfo) {
+ super(id, imgInfo);
+ }
+
+ @Override
+ public final boolean allowsMultiple() {
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ final PngChunkSingle other = (PngChunkSingle) obj;
+ if (id == null) {
+ if (other.id != null)
+ return false;
+ } else if (!id.equals(other.id))
+ return false;
+ return true;
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSkipped.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSkipped.java
new file mode 100644
index 000000000..02a18816b
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSkipped.java
@@ -0,0 +1,41 @@
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngjException;
+
+/**
+ * Pseudo chunk type, for chunks that were skipped on reading
+ */
+public class PngChunkSkipped extends PngChunk {
+
+ public PngChunkSkipped(final String id, final ImageInfo info, final int clen) {
+ super(id, info);
+ this.length = clen;
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NONE;
+ }
+
+ @Override
+ public ChunkRaw createRawChunk() {
+ throw new PngjException("Non supported for a skipped chunk");
+ }
+
+ @Override
+ public void parseFromRaw(final ChunkRaw c) {
+ throw new PngjException("Non supported for a skipped chunk");
+ }
+
+ @Override
+ public void cloneDataFromRead(final PngChunk other) {
+ throw new PngjException("Non supported for a skipped chunk");
+ }
+
+ @Override
+ public boolean allowsMultiple() {
+ return true;
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java
index c535fe34a..634d0cd12 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java
@@ -1,33 +1,45 @@
package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
+import jogamp.opengl.util.pngj.PngjException;
+/**
+ * tEXt chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11tEXt
+ */
public class PngChunkTEXT extends PngChunkTextVar {
- public PngChunkTEXT(ImageInfo info) {
- super(ChunkHelper.tEXt, info);
+ public final static String ID = ChunkHelper.tEXt;
+
+ public PngChunkTEXT(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public ChunkRaw createChunk() {
- if (val.isEmpty() || key.isEmpty())
- return null;
- byte[] b = (key + "\0" + val).getBytes(PngHelper.charsetLatin1);
- ChunkRaw chunk = createEmptyChunk(b.length, false);
+ public ChunkRaw createRawChunk() {
+ if (key.isEmpty())
+ throw new PngjException("Text chunk key must be non empty");
+ final byte[] b = (key + "\0" + val).getBytes(PngHelperInternal.charsetLatin1);
+ final ChunkRaw chunk = createEmptyChunk(b.length, false);
chunk.data = b;
return chunk;
}
@Override
- public void parseFromChunk(ChunkRaw c) {
- String[] k = (new String(c.data, PngHelper.charsetLatin1)).split("\0");
- key = k[0];
- val = k[1];
+ public void parseFromRaw(final ChunkRaw c) {
+ int i;
+ for (i = 0; i < c.data.length; i++)
+ if (c.data[i] == 0)
+ break;
+ key = new String(c.data, 0, i, PngHelperInternal.charsetLatin1);
+ i++;
+ val = i < c.data.length ? new String(c.data, i, c.data.length - i, PngHelperInternal.charsetLatin1) : "";
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkTEXT otherx = (PngChunkTEXT) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkTEXT otherx = (PngChunkTEXT) other;
key = otherx.key;
val = otherx.val;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTIME.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTIME.java
index 37e617acb..850fb649f 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTIME.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTIME.java
@@ -3,22 +3,33 @@ package jogamp.opengl.util.pngj.chunks;
import java.util.Calendar;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
+/**
+ * tIME chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11tIME
+ */
+public class PngChunkTIME extends PngChunkSingle {
+ public final static String ID = ChunkHelper.tIME;
-public class PngChunkTIME extends PngChunk {
// http://www.w3.org/TR/PNG/#11tIME
private int year, mon, day, hour, min, sec;
- public PngChunkTIME(ImageInfo info) {
- super(ChunkHelper.tIME, info);
+ public PngChunkTIME(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public ChunkRaw createChunk() {
- ChunkRaw c = createEmptyChunk(7, true);
- PngHelper.writeInt2tobytes(year, c.data, 0);
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NONE;
+ }
+
+ @Override
+ public ChunkRaw createRawChunk() {
+ final ChunkRaw c = createEmptyChunk(7, true);
+ PngHelperInternal.writeInt2tobytes(year, c.data, 0);
c.data[2] = (byte) mon;
c.data[3] = (byte) day;
c.data[4] = (byte) hour;
@@ -28,20 +39,20 @@ public class PngChunkTIME extends PngChunk {
}
@Override
- public void parseFromChunk(ChunkRaw chunk) {
+ public void parseFromRaw(final ChunkRaw chunk) {
if (chunk.len != 7)
throw new PngjException("bad chunk " + chunk);
- year = PngHelper.readInt2fromBytes(chunk.data, 0);
- mon = PngHelper.readInt1fromByte(chunk.data, 2);
- day = PngHelper.readInt1fromByte(chunk.data, 3);
- hour = PngHelper.readInt1fromByte(chunk.data, 4);
- min = PngHelper.readInt1fromByte(chunk.data, 5);
- sec = PngHelper.readInt1fromByte(chunk.data, 6);
+ year = PngHelperInternal.readInt2fromBytes(chunk.data, 0);
+ mon = PngHelperInternal.readInt1fromByte(chunk.data, 2);
+ day = PngHelperInternal.readInt1fromByte(chunk.data, 3);
+ hour = PngHelperInternal.readInt1fromByte(chunk.data, 4);
+ min = PngHelperInternal.readInt1fromByte(chunk.data, 5);
+ sec = PngHelperInternal.readInt1fromByte(chunk.data, 6);
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkTIME x = (PngChunkTIME) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkTIME x = (PngChunkTIME) other;
year = x.year;
mon = x.mon;
day = x.day;
@@ -50,8 +61,8 @@ public class PngChunkTIME extends PngChunk {
sec = x.sec;
}
- public void setNow(int secsAgo) {
- Calendar d = Calendar.getInstance();
+ public void setNow(final int secsAgo) {
+ final Calendar d = Calendar.getInstance();
d.setTimeInMillis(System.currentTimeMillis() - 1000 * (long) secsAgo);
year = d.get(Calendar.YEAR);
mon = d.get(Calendar.MONTH) + 1;
@@ -61,7 +72,7 @@ public class PngChunkTIME extends PngChunk {
sec = d.get(Calendar.SECOND);
}
- public void setYMDHMS(int yearx, int monx, int dayx, int hourx, int minx, int secx) {
+ public void setYMDHMS(final int yearx, final int monx, final int dayx, final int hourx, final int minx, final int secx) {
year = yearx;
mon = monx;
day = dayx;
@@ -69,15 +80,14 @@ public class PngChunkTIME extends PngChunk {
min = minx;
sec = secx;
}
+
public int[] getYMDHMS() {
return new int[] { year, mon, day, hour, min, sec };
}
/** format YYYY/MM/DD HH:mm:SS */
public String getAsString() {
- return String.format("%04/%02d/%02d %02d:%02d:%02d", year, mon, day, hour, min, sec);
+ return String.format("%04d/%02d/%02d %02d:%02d:%02d", year, mon, day, hour, min, sec);
}
-
-
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTRNS.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTRNS.java
index 9365e5e8e..5e10c041b 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTRNS.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTRNS.java
@@ -1,129 +1,141 @@
-package jogamp.opengl.util.pngj.chunks;
-
-import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
-import jogamp.opengl.util.pngj.PngjException;
-
-/*
- */
-public class PngChunkTRNS extends PngChunk {
- // http://www.w3.org/TR/PNG/#11tRNS
- // this chunk structure depends on the image type
- // only one of these is meaningful
- private int gray;
- private int red, green, blue;
- private int[] paletteAlpha = new int[] {};
-
- public PngChunkTRNS(ImageInfo info) {
- super(ChunkHelper.tRNS, info);
- }
-
- @Override
- public boolean mustGoBeforeIDAT() {
- return true;
- }
-
- @Override
- public boolean mustGoAfterPLTE() {
- return true;
- }
-
- @Override
- public ChunkRaw createChunk() {
- ChunkRaw c = null;
- if (imgInfo.greyscale) {
- c = createEmptyChunk(2, true);
- PngHelper.writeInt2tobytes(gray, c.data, 0);
- } else if (imgInfo.indexed) {
- c = createEmptyChunk(paletteAlpha.length, true);
- for (int n = 0; n < c.len; n++) {
- c.data[n] = (byte) paletteAlpha[n];
- }
- } else {
- c = createEmptyChunk(6, true);
- PngHelper.writeInt2tobytes(red, c.data, 0);
- PngHelper.writeInt2tobytes(green, c.data, 0);
- PngHelper.writeInt2tobytes(blue, c.data, 0);
- }
- return c;
- }
-
- @Override
- public void parseFromChunk(ChunkRaw c) {
- if (imgInfo.greyscale) {
- gray = PngHelper.readInt2fromBytes(c.data, 0);
- } else if (imgInfo.indexed) {
- int nentries = c.data.length;
- paletteAlpha = new int[nentries];
- for (int n = 0; n < nentries; n++) {
- paletteAlpha[n] = (int) (c.data[n] & 0xff);
- }
- } else {
- red = PngHelper.readInt2fromBytes(c.data, 0);
- green = PngHelper.readInt2fromBytes(c.data, 2);
- blue = PngHelper.readInt2fromBytes(c.data, 4);
- }
- }
-
- @Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkTRNS otherx = (PngChunkTRNS) other;
- gray = otherx.gray;
- red = otherx.red;
- green = otherx.red;
- blue = otherx.red;
- if (otherx.paletteAlpha != null) {
- paletteAlpha = new int[otherx.paletteAlpha.length];
- System.arraycopy(otherx.paletteAlpha, 0, paletteAlpha, 0, paletteAlpha.length);
- }
- }
-
- /**
- * Set rgb values
- *
- */
- public void setRGB(int r, int g, int b) {
- if (imgInfo.greyscale || imgInfo.indexed)
- throw new PngjException("only rgb or rgba images support this");
- red = r;
- green = g;
- blue = b;
- }
-
- public int[] getRGB() {
- if (imgInfo.greyscale || imgInfo.indexed)
- throw new PngjException("only rgb or rgba images support this");
- return new int[] { red, green, blue };
- }
-
- public void setGray(int g) {
- if (!imgInfo.greyscale)
- throw new PngjException("only grayscale images support this");
- gray = g;
- }
-
- public int getGray() {
- if (!imgInfo.greyscale)
- throw new PngjException("only grayscale images support this");
- return gray;
- }
-
- /**
- * WARNING: non deep copy
- */
- public void setPalletteAlpha(int[] palAlpha) {
- if (!imgInfo.indexed)
- throw new PngjException("only indexed images support this");
- paletteAlpha = palAlpha;
- }
-
- /**
- * WARNING: non deep copy
- */
- public int[] getPalletteAlpha() {
- if (!imgInfo.indexed)
- throw new PngjException("only indexed images support this");
- return paletteAlpha;
- }
-
-}
+package jogamp.opengl.util.pngj.chunks;
+
+import jogamp.opengl.util.pngj.ImageInfo;
+import jogamp.opengl.util.pngj.PngHelperInternal;
+import jogamp.opengl.util.pngj.PngjException;
+
+/**
+ * tRNS chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11tRNS
+ * <p>
+ * this chunk structure depends on the image type
+ */
+public class PngChunkTRNS extends PngChunkSingle {
+ public final static String ID = ChunkHelper.tRNS;
+
+ // http://www.w3.org/TR/PNG/#11tRNS
+
+ // only one of these is meaningful, depending on the image type
+ private int gray;
+ private int red, green, blue;
+ private int[] paletteAlpha = new int[] {};
+
+ public PngChunkTRNS(final ImageInfo info) {
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.AFTER_PLTE_BEFORE_IDAT;
+ }
+
+ @Override
+ public ChunkRaw createRawChunk() {
+ ChunkRaw c = null;
+ if (imgInfo.greyscale) {
+ c = createEmptyChunk(2, true);
+ PngHelperInternal.writeInt2tobytes(gray, c.data, 0);
+ } else if (imgInfo.indexed) {
+ c = createEmptyChunk(paletteAlpha.length, true);
+ for (int n = 0; n < c.len; n++) {
+ c.data[n] = (byte) paletteAlpha[n];
+ }
+ } else {
+ c = createEmptyChunk(6, true);
+ PngHelperInternal.writeInt2tobytes(red, c.data, 0);
+ PngHelperInternal.writeInt2tobytes(green, c.data, 0);
+ PngHelperInternal.writeInt2tobytes(blue, c.data, 0);
+ }
+ return c;
+ }
+
+ @Override
+ public void parseFromRaw(final ChunkRaw c) {
+ if (imgInfo.greyscale) {
+ gray = PngHelperInternal.readInt2fromBytes(c.data, 0);
+ } else if (imgInfo.indexed) {
+ final int nentries = c.data.length;
+ paletteAlpha = new int[nentries];
+ for (int n = 0; n < nentries; n++) {
+ paletteAlpha[n] = c.data[n] & 0xff;
+ }
+ } else {
+ red = PngHelperInternal.readInt2fromBytes(c.data, 0);
+ green = PngHelperInternal.readInt2fromBytes(c.data, 2);
+ blue = PngHelperInternal.readInt2fromBytes(c.data, 4);
+ }
+ }
+
+ @Override
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkTRNS otherx = (PngChunkTRNS) other;
+ gray = otherx.gray;
+ red = otherx.red;
+ green = otherx.green;
+ blue = otherx.blue;
+ if (otherx.paletteAlpha != null) {
+ paletteAlpha = new int[otherx.paletteAlpha.length];
+ System.arraycopy(otherx.paletteAlpha, 0, paletteAlpha, 0, paletteAlpha.length);
+ }
+ }
+
+ /**
+ * Set rgb values
+ *
+ */
+ public void setRGB(final int r, final int g, final int b) {
+ if (imgInfo.greyscale || imgInfo.indexed)
+ throw new PngjException("only rgb or rgba images support this");
+ red = r;
+ green = g;
+ blue = b;
+ }
+
+ public int[] getRGB() {
+ if (imgInfo.greyscale || imgInfo.indexed)
+ throw new PngjException("only rgb or rgba images support this");
+ return new int[] { red, green, blue };
+ }
+
+ public void setGray(final int g) {
+ if (!imgInfo.greyscale)
+ throw new PngjException("only grayscale images support this");
+ gray = g;
+ }
+
+ public int getGray() {
+ if (!imgInfo.greyscale)
+ throw new PngjException("only grayscale images support this");
+ return gray;
+ }
+
+ /**
+ * WARNING: non deep copy
+ */
+ public void setPalletteAlpha(final int[] palAlpha) {
+ if (!imgInfo.indexed)
+ throw new PngjException("only indexed images support this");
+ paletteAlpha = palAlpha;
+ }
+
+ /**
+ * to use when only one pallete index is set as totally transparent
+ */
+ public void setIndexEntryAsTransparent(final int palAlphaIndex) {
+ if (!imgInfo.indexed)
+ throw new PngjException("only indexed images support this");
+ paletteAlpha = new int[] { palAlphaIndex + 1 };
+ for (int i = 0; i < palAlphaIndex; i++)
+ paletteAlpha[i] = 255;
+ paletteAlpha[palAlphaIndex] = 0;
+ }
+
+ /**
+ * WARNING: non deep copy
+ */
+ public int[] getPalletteAlpha() {
+ return paletteAlpha;
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTextVar.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTextVar.java
index 3d92a806f..d00c29971 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTextVar.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTextVar.java
@@ -3,11 +3,9 @@ package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
/**
- * superclass for three textual chunks (TEXT, ITXT, ZTXT)
- *
- * @author Hernan J Gonzalez
+ * Superclass (abstract) for three textual chunks (TEXT, ITXT, ZTXT)
*/
-public abstract class PngChunkTextVar extends PngChunk {
+public abstract class PngChunkTextVar extends PngChunkMultiple {
protected String key; // key/val: only for tEXt. lazy computed
protected String val;
@@ -23,13 +21,13 @@ public abstract class PngChunkTextVar extends PngChunk {
public final static String KEY_Source = "Source"; // Device used to create the image
public final static String KEY_Comment = "Comment"; // Miscellaneous comment
- protected PngChunkTextVar(String id, ImageInfo info) {
+ protected PngChunkTextVar(final String id, final ImageInfo info) {
super(id, info);
}
@Override
- public boolean allowsMultiple() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NONE;
}
public static class PngTxtInfo {
@@ -53,7 +51,7 @@ public abstract class PngChunkTextVar extends PngChunk {
return val;
}
- public void setKeyVal(String key, String val) {
+ public void setKeyVal(final String key, final String val) {
this.key = key;
this.val = val;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java
index 15a35935a..693729e9b 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java
@@ -2,33 +2,38 @@ package jogamp.opengl.util.pngj.chunks;
import jogamp.opengl.util.pngj.ImageInfo;
-public class PngChunkUNKNOWN extends PngChunk { // unkown, custom or not
+/**
+ * Placeholder for UNKNOWN (custom or not) chunks.
+ * <p>
+ * For PngReader, a chunk is unknown if it's not registered in the chunk factory
+ */
+public class PngChunkUNKNOWN extends PngChunkMultiple { // unkown, custom or not
private byte[] data;
- public PngChunkUNKNOWN(String id, ImageInfo info) {
+ public PngChunkUNKNOWN(final String id, final ImageInfo info) {
super(id, info);
}
- @Override
- public boolean allowsMultiple() {
- return true;
- }
-
- private PngChunkUNKNOWN(PngChunkUNKNOWN c, ImageInfo info) {
+ private PngChunkUNKNOWN(final PngChunkUNKNOWN c, final ImageInfo info) {
super(c.id, info);
System.arraycopy(c.data, 0, data, 0, c.data.length);
}
@Override
- public ChunkRaw createChunk() {
- ChunkRaw p = createEmptyChunk(data.length, false);
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NONE;
+ }
+
+ @Override
+ public ChunkRaw createRawChunk() {
+ final ChunkRaw p = createEmptyChunk(data.length, false);
p.data = this.data;
return p;
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
data = c.data;
}
@@ -38,14 +43,14 @@ public class PngChunkUNKNOWN extends PngChunk { // unkown, custom or not
}
/* does not copy! */
- public void setData(byte[] data) {
+ public void setData(final byte[] data) {
this.data = data;
}
@Override
- public void cloneDataFromRead(PngChunk other) {
+ public void cloneDataFromRead(final PngChunk other) {
// THIS SHOULD NOT BE CALLED IF ALREADY CLONED WITH COPY CONSTRUCTOR
- PngChunkUNKNOWN c = (PngChunkUNKNOWN) other;
+ final PngChunkUNKNOWN c = (PngChunkUNKNOWN) other;
data = c.data; // not deep copy
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java
index fd6c08273..c1dfdeb33 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java
@@ -4,38 +4,44 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import jogamp.opengl.util.pngj.ImageInfo;
-import jogamp.opengl.util.pngj.PngHelper;
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
-
+/**
+ * zTXt chunk.
+ * <p>
+ * see http://www.w3.org/TR/PNG/#11zTXt
+ */
public class PngChunkZTXT extends PngChunkTextVar {
+ public final static String ID = ChunkHelper.zTXt;
+
// http://www.w3.org/TR/PNG/#11zTXt
- public PngChunkZTXT(ImageInfo info) {
- super(ChunkHelper.zTXt, info);
+ public PngChunkZTXT(final ImageInfo info) {
+ super(ID, info);
}
@Override
- public ChunkRaw createChunk() {
- if (val.isEmpty() || key.isEmpty())
- return null;
+ public ChunkRaw createRawChunk() {
+ if (key.isEmpty())
+ throw new PngjException("Text chunk key must be non empty");
try {
- ByteArrayOutputStream ba = new ByteArrayOutputStream();
- ba.write(key.getBytes(PngHelper.charsetLatin1));
+ final ByteArrayOutputStream ba = new ByteArrayOutputStream();
+ ba.write(key.getBytes(PngHelperInternal.charsetLatin1));
ba.write(0); // separator
ba.write(0); // compression method: 0
- byte[] textbytes = ChunkHelper.compressBytes(val.getBytes(PngHelper.charsetLatin1), true);
+ final byte[] textbytes = ChunkHelper.compressBytes(val.getBytes(PngHelperInternal.charsetLatin1), true);
ba.write(textbytes);
- byte[] b = ba.toByteArray();
- ChunkRaw chunk = createEmptyChunk(b.length, false);
+ final byte[] b = ba.toByteArray();
+ final ChunkRaw chunk = createEmptyChunk(b.length, false);
chunk.data = b;
return chunk;
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new PngjException(e);
}
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(final ChunkRaw c) {
int nullsep = -1;
for (int i = 0; i < c.data.length; i++) { // look for first zero
if (c.data[i] != 0)
@@ -45,17 +51,17 @@ public class PngChunkZTXT extends PngChunkTextVar {
}
if (nullsep < 0 || nullsep > c.data.length - 2)
throw new PngjException("bad zTXt chunk: no separator found");
- key = new String(c.data, 0, nullsep, PngHelper.charsetLatin1);
- int compmet = (int) c.data[nullsep + 1];
+ key = new String(c.data, 0, nullsep, PngHelperInternal.charsetLatin1);
+ final int compmet = c.data[nullsep + 1];
if (compmet != 0)
throw new PngjException("bad zTXt chunk: unknown compression method");
- byte[] uncomp = ChunkHelper.compressBytes(c.data, nullsep + 2, c.data.length - nullsep - 2, false); // uncompress
- val = new String(uncomp, PngHelper.charsetLatin1);
+ final byte[] uncomp = ChunkHelper.compressBytes(c.data, nullsep + 2, c.data.length - nullsep - 2, false); // uncompress
+ val = new String(uncomp, PngHelperInternal.charsetLatin1);
}
@Override
- public void cloneDataFromRead(PngChunk other) {
- PngChunkZTXT otherx = (PngChunkZTXT) other;
+ public void cloneDataFromRead(final PngChunk other) {
+ final PngChunkZTXT otherx = (PngChunkZTXT) other;
key = otherx.key;
val = otherx.val;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java
index a82754588..bba2b3e7c 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java
@@ -1,106 +1,150 @@
package jogamp.opengl.util.pngj.chunks;
-import jogamp.opengl.util.pngj.PngHelper;
+import java.util.ArrayList;
+import java.util.List;
+
+import jogamp.opengl.util.pngj.PngHelperInternal;
import jogamp.opengl.util.pngj.PngjException;
/**
- * We consider "image metadata" every info inside the image except for the most basic image info (IHDR chunk - ImageInfo
- * class) and the pixels values.
- *
+ * We consider "image metadata" every info inside the image except for the most
+ * basic image info (IHDR chunk - ImageInfo class) and the pixels values.
+ * <p>
* This includes the palette (if present) and all the ancillary chunks
- *
- * This class provides a wrapper over the collection of chunks of a image (read or to write) and provides some high
- * level methods to access them
- *
+ * <p>
+ * This class provides a wrapper over the collection of chunks of a image (read
+ * or to write) and provides some high level methods to access them
*/
public class PngMetadata {
- private final ChunkList chunkList;
+ private final ChunksList chunkList;
private final boolean readonly;
- public PngMetadata(ChunkList chunks, boolean readonly) {
+ public PngMetadata(final ChunksList chunks) {
this.chunkList = chunks;
- this.readonly = readonly;
+ if (chunks instanceof ChunksListForWrite) {
+ this.readonly = false;
+ } else {
+ this.readonly = true;
+ }
}
/**
* Queues the chunk at the writer
+ * <p>
+ * lazyOverwrite: if true, checks if there is a queued "equivalent" chunk
+ * and if so, overwrites it. However if that not check for already written
+ * chunks.
*/
- public boolean setChunk(PngChunk c, boolean overwriteIfPresent) {
+ public void queueChunk(final PngChunk c, final boolean lazyOverwrite) {
+ final ChunksListForWrite cl = getChunkListW();
if (readonly)
throw new PngjException("cannot set chunk : readonly metadata");
- return chunkList.setChunk(c, overwriteIfPresent);
+ if (lazyOverwrite) {
+ ChunkHelper.trimList(cl.getQueuedChunks(), new ChunkPredicate() {
+ @Override
+ public boolean match(final PngChunk c2) {
+ return ChunkHelper.equivalent(c, c2);
+ }
+ });
+ }
+ cl.queue(c);
}
-
- /**
- * Returns only one chunk or null if nothing found - does not include queued chunks
- *
- * If more than one chunk (after filtering by inner id) is found, then an exception is thrown (failifMultiple=true)
- * or the last one is returned (failifMultiple=false)
- *
- * @param id Chunk id
- * @param innerid if not null, the chunk is assumed to be PngChunkTextVar or PngChunkSPLT, and filtered by that 'internal id'
- * @param failIfMultiple throw exception if more that one
- * @return chunk (not cloned)
- */
- public PngChunk getChunk1(String id, String innerid, boolean failIfMultiple) {
- return chunkList.getChunk1(id, innerid, failIfMultiple);
+ public void queueChunk(final PngChunk c) {
+ queueChunk(c, true);
}
- /**
- * Same as getChunk1(id, innerid=null, failIfMultiple=true);
- */
- public PngChunk getChunk1(String id) {
- return chunkList.getChunk1(id);
+ private ChunksListForWrite getChunkListW() {
+ return (ChunksListForWrite) chunkList;
}
// ///// high level utility methods follow ////////////
// //////////// DPI
- /**
- * returns -1 if not found or dimension unknown
- **/
+ /**
+ * returns -1 if not found or dimension unknown
+ */
public double[] getDpi() {
- PngChunk c = getChunk1(ChunkHelper.pHYs, null, true);
+ final PngChunk c = chunkList.getById1(ChunkHelper.pHYs, true);
if (c == null)
return new double[] { -1, -1 };
else
return ((PngChunkPHYS) c).getAsDpi2();
}
- public void setDpi(double x) {
+ public void setDpi(final double x) {
setDpi(x, x);
}
- public void setDpi(double x, double y) {
- PngChunkPHYS c = new PngChunkPHYS(chunkList.imageInfo);
+ public void setDpi(final double x, final double y) {
+ final PngChunkPHYS c = new PngChunkPHYS(chunkList.imageInfo);
c.setAsDpi2(x, y);
- setChunk(c, true);
+ queueChunk(c);
}
// //////////// TIME
- public void setTimeNow(int secsAgo) {
- PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo);
+ /**
+ * Creates a time chunk with current time, less secsAgo seconds
+ * <p>
+ *
+ * @return Returns the created-queued chunk, just in case you want to
+ * examine or modify it
+ */
+ public PngChunkTIME setTimeNow(final int secsAgo) {
+ final PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo);
c.setNow(secsAgo);
- setChunk(c, true);
+ queueChunk(c);
+ return c;
+ }
+
+ public PngChunkTIME setTimeNow() {
+ return setTimeNow(0);
}
- public void setTimeYMDHMS(int yearx, int monx, int dayx, int hourx, int minx, int secx) {
- PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo);
+ /**
+ * Creates a time chunk with diven date-time
+ * <p>
+ *
+ * @return Returns the created-queued chunk, just in case you want to
+ * examine or modify it
+ */
+ public PngChunkTIME setTimeYMDHMS(final int yearx, final int monx, final int dayx, final int hourx, final int minx, final int secx) {
+ final PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo);
c.setYMDHMS(yearx, monx, dayx, hourx, minx, secx);
- setChunk(c, true);
+ queueChunk(c, true);
+ return c;
+ }
+
+ /**
+ * null if not found
+ */
+ public PngChunkTIME getTime() {
+ return (PngChunkTIME) chunkList.getById1(ChunkHelper.tIME);
}
public String getTimeAsString() {
- PngChunk c = getChunk1(ChunkHelper.tIME, null, true);
- return c != null ? ((PngChunkTIME) c).getAsString() : "";
+ final PngChunkTIME c = getTime();
+ return c == null ? "" : c.getAsString();
}
// //////////// TEXT
- public void setText(String k, String val, boolean useLatin1, boolean compress) {
+ /**
+ * Creates a text chunk and queue it.
+ * <p>
+ *
+ * @param k
+ * : key (latin1)
+ * @param val
+ * (arbitrary, should be latin1 if useLatin1)
+ * @param useLatin1
+ * @param compress
+ * @return Returns the created-queued chunks, just in case you want to
+ * examine, touch it
+ */
+ public PngChunkTextVar setText(final String k, final String val, final boolean useLatin1, final boolean compress) {
if (compress && !useLatin1)
throw new PngjException("cannot compress non latin text");
PngChunkTextVar c;
@@ -115,21 +159,84 @@ public class PngMetadata {
((PngChunkITXT) c).setLangtag(k); // we use the same orig tag (this is not quite right)
}
c.setKeyVal(k, val);
- setChunk(c, true);
+ queueChunk(c, true);
+ return c;
}
- public void setText(String k, String val) {
- setText(k, val, false, val.length() > 400);
+ public PngChunkTextVar setText(final String k, final String val) {
+ return setText(k, val, false, false);
}
- /** tries all text chunks - returns null if not found */
- public String getTxtForKey(String k) {
- PngChunk c = getChunk1(ChunkHelper.tEXt, k, true);
- if (c == null)
- c = getChunk1(ChunkHelper.zTXt, k, true);
- if (c == null)
- c = getChunk1(ChunkHelper.iTXt, k, true);
- return c != null ? ((PngChunkTextVar) c).getVal() : null;
+ /**
+ * gets all text chunks with a given key
+ * <p>
+ * returns null if not found
+ * <p>
+ * Warning: this does not check the "lang" key of iTxt
+ */
+ @SuppressWarnings("unchecked")
+ public List<? extends PngChunkTextVar> getTxtsForKey(final String k) {
+ @SuppressWarnings("rawtypes")
+ final
+ List c = new ArrayList();
+ c.addAll(chunkList.getById(ChunkHelper.tEXt, k));
+ c.addAll(chunkList.getById(ChunkHelper.zTXt, k));
+ c.addAll(chunkList.getById(ChunkHelper.iTXt, k));
+ return c;
+ }
+
+ /**
+ * Returns empty if not found, concatenated (with newlines) if multiple! -
+ * and trimmed
+ * <p>
+ * Use getTxtsForKey() if you don't want this behaviour
+ */
+ public String getTxtForKey(final String k) {
+ final List<? extends PngChunkTextVar> li = getTxtsForKey(k);
+ if (li.isEmpty())
+ return "";
+ final StringBuilder t = new StringBuilder();
+ for (final PngChunkTextVar c : li)
+ t.append(c.getVal()).append("\n");
+ return t.toString().trim();
+ }
+
+ /**
+ * Returns the palette chunk, if present
+ *
+ * @return null if not present
+ */
+ public PngChunkPLTE getPLTE() {
+ return (PngChunkPLTE) chunkList.getById1(PngChunkPLTE.ID);
+ }
+
+ /**
+ * Creates a new empty palette chunk, queues it for write and return it to
+ * the caller, who should fill its entries
+ */
+ public PngChunkPLTE createPLTEChunk() {
+ final PngChunkPLTE plte = new PngChunkPLTE(chunkList.imageInfo);
+ queueChunk(plte);
+ return plte;
+ }
+
+ /**
+ * Returns the TRNS chunk, if present
+ *
+ * @return null if not present
+ */
+ public PngChunkTRNS getTRNS() {
+ return (PngChunkTRNS) chunkList.getById1(PngChunkTRNS.ID);
+ }
+
+ /**
+ * Creates a new empty TRNS chunk, queues it for write and return it to the
+ * caller, who should fill its entries
+ */
+ public PngChunkTRNS createTRNSChunk() {
+ final PngChunkTRNS trns = new PngChunkTRNS(chunkList.imageInfo);
+ queueChunk(trns);
+ return trns;
}
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/package.html b/src/jogl/classes/jogamp/opengl/util/pngj/package.html
index 209b39c59..0b0e2c8c1 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/package.html
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/package.html
@@ -1,11 +1,10 @@
<html>
<body bgcolor="white">
<p>
-Contains the main classes for the PNGJ library.<p>
-Client code should rarely need more than the public members of this package.
+PNGJ main package
</p>
<p>
-See also the <code>nosandbox</code> package if available.
+Client code should rarely need more than the public members of this package.
</p>
</body>
</html>
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/DistortionMesh.java b/src/jogl/classes/jogamp/opengl/util/stereo/DistortionMesh.java
new file mode 100644
index 000000000..7a2483121
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/DistortionMesh.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright 2014 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.stereo;
+
+import com.jogamp.opengl.util.stereo.EyeParameter;
+
+public class DistortionMesh {
+ public static interface Producer {
+ /** Initialize */
+ void init(final GenericStereoDevice.Config deviceConfig, final float[] eyeReliefInMeters);
+
+ /** Distortion Mesh Producer */
+ DistortionMesh create(final EyeParameter eyeParam, final int distortionBits);
+ }
+ public static class DistortionVertex {
+ /** {@value} */
+ public static final int def_pos_size = 2;
+ /** {@value} */
+ public static final int def_vignetteFactor_size = 1;
+ /** {@value} */
+ public static final int def_timewarpFactor_size = 1;
+ /** {@value} */
+ public static final int def_texR_size = 2;
+ /** {@value} */
+ public static final int def_texG_size = 2;
+ /** {@value} */
+ public static final int def_texB_size = 2;
+
+ /** {@value} */
+ public static final int def_total_size = def_pos_size + def_vignetteFactor_size + def_timewarpFactor_size +
+ def_texR_size + def_texG_size + def_texB_size;
+
+ public DistortionVertex(final float[] data, final int pos_size,
+ final int vignetteFactor_size, final int timewarpFactor_size, final int texR_size,
+ final int texG_size, final int texB_size) {
+ this.data = data;
+ this.pos_size = pos_size;
+ this.vignetteFactor_size = vignetteFactor_size;
+ this.timewarpFactor_size = timewarpFactor_size;
+ this.texR_size = texR_size;
+ this.texG_size = texG_size;
+ this.texB_size = texB_size;
+ }
+ final float[] data;
+
+ /** Usually {@link #def_pos_size} */
+ final int pos_size;
+ /** Usually {@link #def_vignetteFactor_size} */
+ final int vignetteFactor_size;
+ /** Usually {@link #def_timewarpFactor_size} */
+ final int timewarpFactor_size;
+ /** Usually {@link #def_texR_size} */
+ final int texR_size;
+ /** Usually {@link #def_texG_size} */
+ final int texG_size;
+ /** Usually {@link #def_texB_size} */
+ final int texB_size;
+ }
+ public DistortionMesh(final DistortionMesh.DistortionVertex[] vertices, final int vertexCount,
+ final short[] indices, final int indexCount) {
+ this.vertices = vertices;
+ this.vertexCount = vertexCount;
+ this.indices = indices;
+ this.indexCount = indexCount;
+ }
+ final DistortionMesh.DistortionVertex[] vertices;
+ final int vertexCount;
+ final short[] indices;
+ final int indexCount;
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDevice.java b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDevice.java
new file mode 100644
index 000000000..36e8bc5a5
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDevice.java
@@ -0,0 +1,468 @@
+/**
+ * Copyright 2014 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.stereo;
+
+import java.util.Arrays;
+
+import javax.media.nativewindow.util.Dimension;
+import javax.media.nativewindow.util.DimensionImmutable;
+import javax.media.nativewindow.util.Point;
+import javax.media.nativewindow.util.PointImmutable;
+import javax.media.nativewindow.util.Rectangle;
+import javax.media.nativewindow.util.RectangleImmutable;
+
+import com.jogamp.common.util.ReflectionUtil;
+import com.jogamp.opengl.math.FloatUtil;
+import com.jogamp.opengl.math.FovHVHalves;
+import com.jogamp.opengl.util.stereo.EyeParameter;
+import com.jogamp.opengl.util.stereo.StereoDevice;
+import com.jogamp.opengl.util.stereo.StereoDeviceFactory;
+import com.jogamp.opengl.util.stereo.StereoDeviceRenderer;
+import com.jogamp.opengl.util.stereo.StereoUtil;
+
+public class GenericStereoDevice implements StereoDevice {
+ public static enum ShutterType {
+ Global, RollingLeftToRight, RollingRightToLeft, RollingTopToBottom
+ }
+ public static class Config extends StereoDevice.Config {
+ public Config(final String name,
+ final ShutterType shutterType,
+ final DimensionImmutable surfaceSizeInPixels,
+ final float[] screenSizeInMeters,
+ final DimensionImmutable eyeTextureSize,
+ final float pupilCenterFromScreenTopInMeters,
+ final float interpupillaryDistanceInMeters,
+ final int[] eyeRenderOrder,
+ final EyeParameter[] defaultEyeParam,
+ final DistortionMesh.Producer distortionMeshProducer,
+ final int supportedDistortionBits,
+ final int recommendedDistortionBits,
+ final int minimumDistortionBits
+ ) {
+ this.name = name;
+ this.shutterType = shutterType;
+ this.surfaceSizeInPixels = surfaceSizeInPixels;
+ this.screenSizeInMeters = screenSizeInMeters;
+ this.eyeTextureSize = eyeTextureSize;
+ this.pupilCenterFromScreenTopInMeters = pupilCenterFromScreenTopInMeters;
+ this.interpupillaryDistanceInMeters = interpupillaryDistanceInMeters;
+ this.eyeRenderOrder = eyeRenderOrder;
+ this.defaultEyeParam = defaultEyeParam;
+ this.distortionMeshProducer = distortionMeshProducer;
+ this.supportedDistortionBits = supportedDistortionBits;
+ this.recommendedDistortionBits = recommendedDistortionBits;
+ this.minimumDistortionBits = minimumDistortionBits;
+ this.pupilCenterFromTopLeft = new float[2][2];
+ calcPupilCenterFromTopLeft();
+ }
+ /** A variation w/ different surface/screen specs */
+ public Config(final Config source,
+ final DimensionImmutable surfaceSizeInPixels,
+ final float[] screenSizeInMeters,
+ final DimensionImmutable eyeTextureSize) {
+ this.name = source.name;
+ this.shutterType = source.shutterType;
+ this.surfaceSizeInPixels = surfaceSizeInPixels;
+ this.screenSizeInMeters = screenSizeInMeters;
+ this.eyeTextureSize = eyeTextureSize;
+ this.pupilCenterFromScreenTopInMeters = source.pupilCenterFromScreenTopInMeters;
+ this.interpupillaryDistanceInMeters = source.interpupillaryDistanceInMeters;
+ this.eyeRenderOrder = source.eyeRenderOrder;
+ this.defaultEyeParam = source.defaultEyeParam;
+ this.distortionMeshProducer = source.distortionMeshProducer;
+ this.supportedDistortionBits = source.supportedDistortionBits;
+ this.recommendedDistortionBits = source.recommendedDistortionBits;
+ this.minimumDistortionBits = source.minimumDistortionBits;
+ this.pupilCenterFromTopLeft = new float[2][2];
+ calcPupilCenterFromTopLeft();
+ }
+ private void calcPupilCenterFromTopLeft() {
+ final float visibleWidthOfOneEye = 0.5f * screenSizeInMeters[0];
+ final float leftPupilCenterFromLeftInMeters = ( screenSizeInMeters[0] - interpupillaryDistanceInMeters ) * 0.5f;
+ final float rightPupilCenterFromMiddleInMeters = leftPupilCenterFromLeftInMeters + interpupillaryDistanceInMeters - visibleWidthOfOneEye;
+ pupilCenterFromTopLeft[0][0] = leftPupilCenterFromLeftInMeters / visibleWidthOfOneEye;
+ pupilCenterFromTopLeft[0][1] = pupilCenterFromScreenTopInMeters / screenSizeInMeters[1];
+ pupilCenterFromTopLeft[1][0] = rightPupilCenterFromMiddleInMeters / visibleWidthOfOneEye;
+ pupilCenterFromTopLeft[1][1] = pupilCenterFromTopLeft[0][1];
+ }
+
+ /**
+ * Return the vertical pupil center from the screen top in the range [0..1].
+ * @param screenHeightInMeters
+ * @param pupilCenterFromScreenTopInMeters
+ */
+ public static float getVertPupilCenterFromTop(final float screenHeightInMeters, final float pupilCenterFromScreenTopInMeters) {
+ return pupilCenterFromScreenTopInMeters / screenHeightInMeters;
+ }
+
+ /**
+ * Return the horizontal pupil center from the left side for both eyes in the range [0..1].
+ * <pre>
+ <-------------left eye------------->| |<-----------right eye-------------->
+ <------------------------------------screenSizeInMeters.Width----------------------------------->
+ <------interpupillaryDistanceInMeters------>
+ <--centerFromLeftInMeters->
+ ^
+ center of pupil
+ * </pre>
+ *
+ * @param screenWidthInMeters
+ * @param interpupillaryDistanceInMeters
+ */
+ public static float[] getHorizPupilCenterFromLeft(final float screenWidthInMeters, final float interpupillaryDistanceInMeters) {
+ final float visibleWidthOfOneEye = 0.5f * screenWidthInMeters;
+ final float leftPupilCenterFromLeftInMeters = ( screenWidthInMeters - interpupillaryDistanceInMeters ) * 0.5f;
+ final float rightPupilCenterFromMiddleInMeters = leftPupilCenterFromLeftInMeters + interpupillaryDistanceInMeters - visibleWidthOfOneEye;
+ return new float[] { leftPupilCenterFromLeftInMeters / visibleWidthOfOneEye,
+ rightPupilCenterFromMiddleInMeters / visibleWidthOfOneEye };
+ }
+
+ void init() {
+ final float[] eyeReliefInMeters = new float[defaultEyeParam.length];
+ if( 0 < defaultEyeParam.length ) {
+ eyeReliefInMeters[0] = defaultEyeParam[0].eyeReliefZ;
+ }
+ if( 1 < defaultEyeParam.length ) {
+ eyeReliefInMeters[1] = defaultEyeParam[1].eyeReliefZ;
+ }
+ if( null != distortionMeshProducer ) {
+ distortionMeshProducer.init(this, eyeReliefInMeters);
+ }
+ }
+
+ @Override
+ public String toString() { return "StereoConfig["+name+", shutter "+shutterType+", surfaceSize "+surfaceSizeInPixels+
+ ", screenSize "+screenSizeInMeters[0]+" x "+screenSizeInMeters[0]+
+ " [m], eyeTexSize "+eyeTextureSize+", IPD "+interpupillaryDistanceInMeters+
+ " [m], eyeParam "+Arrays.toString(defaultEyeParam)+
+ ", distortionBits[supported ["+StereoUtil.distortionBitsToString(supportedDistortionBits)+
+ "], recommended ["+StereoUtil.distortionBitsToString(recommendedDistortionBits)+
+ "], minimum ["+StereoUtil.distortionBitsToString(minimumDistortionBits)+"]]]";
+ }
+
+ /** Configuration Name */
+ public final String name;
+ public final ShutterType shutterType;
+
+ public final DimensionImmutable surfaceSizeInPixels;
+ public final float[] screenSizeInMeters;
+ /** Texture size per eye */
+ public final DimensionImmutable eyeTextureSize;
+
+ /** Vertical distance from pupil to screen-top in meters */
+ public final float pupilCenterFromScreenTopInMeters;
+ /** Horizontal interpupillary distance (IPD) in meters */
+ public final float interpupillaryDistanceInMeters;
+ /**
+ * Pupil center from top left per eye, ranging from [0..1], maybe used to produce FovHVHalves,
+ * see {@link #getHorizPupilCenterFromLeft(float, float)} and {@link #getVertPupilCenterFromTop(float, float)}.
+ */
+ public final float[/*per-eye*/][/*xy*/] pupilCenterFromTopLeft;
+ public final int[] eyeRenderOrder;
+ public final EyeParameter[] defaultEyeParam;
+ public final DistortionMesh.Producer distortionMeshProducer;
+
+ public final int supportedDistortionBits;
+ public final int recommendedDistortionBits;
+ public final int minimumDistortionBits;
+ }
+
+ /** A mono view configuration, only one eye is supported */
+ public static final Config config01Mono01;
+
+ /** A default stereo SBS view configuration */
+ public static final Config config02StereoSBS01;
+
+ /** A default stereo SBS lense view configuration, utilizing similar settings as OculusVR DK1 */
+ public static final Config config03StereoSBSLense01;
+
+ private static final Config[] configs;
+
+ static {
+ final float[] DEFAULT_EYE_POSITION_OFFSET_STEREO_LENSES = { 0.0f, 1.6f, -5.0f }; // 1.6 up, 5 forward
+ final float[] DEFAULT_EYE_POSITION_OFFSET_STEREO = { 0.0f, 0.3f, 3.0f }; // 0.3 up, 3 back
+ final float[] DEFAULT_EYE_POSITION_OFFSET_MONO = { 0.0f, 0.0f, 3.0f }; // 3 back
+
+ final DimensionImmutable surfaceSizeInPixel = new Dimension(1280, 800);
+ final float[] screenSizeInMeters = new float[] { 0.1498f, 0.0936f };
+ final float interpupillaryDistanceInMeters = 0.0635f;
+ final float pupilCenterFromScreenTopInMeters = screenSizeInMeters[1] / 2f;
+ final float d2r = FloatUtil.PI / 180.0f;
+ {
+ config01Mono01 = new Config(
+ "Def01Mono01",
+ ShutterType.RollingTopToBottom,
+ surfaceSizeInPixel, // resolution
+ screenSizeInMeters, // screenSize [m]
+ surfaceSizeInPixel, // eye textureSize
+ pupilCenterFromScreenTopInMeters, // pupilCenterFromScreenTop [m]
+ interpupillaryDistanceInMeters, // IPD [m]
+ new int[] { 0 }, // eye order
+ new EyeParameter[] {
+ new EyeParameter(0, DEFAULT_EYE_POSITION_OFFSET_MONO,
+ // degrees: 45/2 l, 45/2 r, 45/2 * aspect t, 45/2 * aspect b
+ FovHVHalves.byFovyRadianAndAspect(45f*d2r, 1280f / 800f),
+ 0f /* distNoseToPupil */, 0f /* verticalDelta */, 0f /* eyeReliefInMeters */) },
+ null, // mash producer distortion bits
+ 0, // supported distortion bits
+ 0, // recommended distortion bits
+ 0 // minimum distortion bits
+ );
+ }
+
+ {
+ final DimensionImmutable eyeTextureSize = new Dimension(surfaceSizeInPixel.getWidth()/2, surfaceSizeInPixel.getHeight());
+ final float[] horizPupilCenterFromLeft = Config.getHorizPupilCenterFromLeft(screenSizeInMeters[0], interpupillaryDistanceInMeters);
+ final float vertPupilCenterFromTop = Config.getVertPupilCenterFromTop(screenSizeInMeters[1], pupilCenterFromScreenTopInMeters);
+ final float fovy = 45f;
+ final float aspect = (float)eyeTextureSize.getWidth() / (float)eyeTextureSize.getHeight();
+ final FovHVHalves defaultSBSEyeFovLeft = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[0]);
+ final FovHVHalves defaultSBSEyeFovRight = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[1]);
+
+ config02StereoSBS01 = new Config(
+ "Def02StereoSBS01",
+ ShutterType.RollingTopToBottom,
+ surfaceSizeInPixel, // resolution
+ screenSizeInMeters, // screenSize [m]
+ eyeTextureSize, // eye textureSize
+ pupilCenterFromScreenTopInMeters, // pupilCenterFromScreenTop [m]
+ interpupillaryDistanceInMeters, // IPD [m]
+ new int[] { 0, 1 }, // eye order
+ new EyeParameter[] {
+ new EyeParameter(0, DEFAULT_EYE_POSITION_OFFSET_STEREO, defaultSBSEyeFovLeft,
+ 0.032f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */),
+ new EyeParameter(1, DEFAULT_EYE_POSITION_OFFSET_STEREO, defaultSBSEyeFovRight,
+ -0.032f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */) },
+ null, // mash producer distortion bits
+ 0, // supported distortion bits
+ 0, // recommended distortion bits
+ 0 // minimum distortion bits
+ );
+ }
+
+ {
+ DistortionMesh.Producer lenseDistMeshProduce = null;
+ try {
+ lenseDistMeshProduce =
+ (DistortionMesh.Producer)
+ ReflectionUtil.createInstance("jogamp.opengl.oculusvr.stereo.lense.DistortionMeshProducer", GenericStereoDevice.class.getClassLoader());
+ } catch (final Throwable t) {
+ if(StereoDevice.DEBUG) { System.err.println("Caught: "+t.getMessage()); t.printStackTrace(); }
+ }
+
+ final DimensionImmutable eyeTextureSize = new Dimension(1122, 1553);
+ final float[] horizPupilCenterFromLeft = Config.getHorizPupilCenterFromLeft(screenSizeInMeters[0], interpupillaryDistanceInMeters);
+ final float vertPupilCenterFromTop = Config.getVertPupilCenterFromTop(screenSizeInMeters[1], pupilCenterFromScreenTopInMeters);
+ final float fovy = 129f;
+ final float aspect = (float)eyeTextureSize.getWidth() / (float)eyeTextureSize.getHeight();
+ final FovHVHalves defaultSBSEyeFovLenseLeft = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[0]);
+ final FovHVHalves defaultSBSEyeFovLenseRight = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[1]);
+
+ config03StereoSBSLense01 = null == lenseDistMeshProduce ? null :
+ new Config(
+ "Def03StereoSBSLense01",
+ ShutterType.RollingTopToBottom,
+ surfaceSizeInPixel, // resolution
+ screenSizeInMeters, // screenSize [m]
+ eyeTextureSize, // eye textureSize
+ pupilCenterFromScreenTopInMeters, // pupilCenterFromScreenTop [m]
+ interpupillaryDistanceInMeters, // IPD [m]
+ new int[] { 0, 1 }, // eye order
+ new EyeParameter[] {
+ new EyeParameter(0, DEFAULT_EYE_POSITION_OFFSET_STEREO_LENSES, defaultSBSEyeFovLenseLeft,
+ 0.032f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */),
+ new EyeParameter(1, DEFAULT_EYE_POSITION_OFFSET_STEREO_LENSES, defaultSBSEyeFovLenseRight,
+ -0.032f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */) },
+ lenseDistMeshProduce,
+ // supported distortion bits
+ StereoDeviceRenderer.DISTORTION_BARREL | StereoDeviceRenderer.DISTORTION_CHROMATIC | StereoDeviceRenderer.DISTORTION_VIGNETTE,
+ // recommended distortion bits
+ StereoDeviceRenderer.DISTORTION_BARREL | StereoDeviceRenderer.DISTORTION_CHROMATIC | StereoDeviceRenderer.DISTORTION_VIGNETTE,
+ // minimum distortion bits
+ StereoDeviceRenderer.DISTORTION_BARREL
+ );
+ }
+ configs = new Config[] { config01Mono01, config02StereoSBS01, config03StereoSBSLense01 };
+ }
+
+ private final StereoDeviceFactory factory;
+ public final int deviceIndex;
+ public final Config config;
+
+ public final Point surfacePos;
+ private final FovHVHalves[] defaultEyeFov;
+
+ private boolean sensorsStarted = false;
+
+ public GenericStereoDevice(final StereoDeviceFactory factory, final int deviceIndex, final StereoDevice.Config customConfig) {
+ this.factory = factory;
+ this.deviceIndex = deviceIndex;
+
+ if( customConfig instanceof GenericStereoDevice.Config) {
+ this.config = (GenericStereoDevice.Config) customConfig;
+ } else {
+ final int cfgIdx = Math.min(deviceIndex % 10, configs.length-1);
+ this.config = null != configs[cfgIdx] ? configs[cfgIdx] : config02StereoSBS01;
+ }
+ config.init();
+
+ this.surfacePos = new Point(0, 0);
+
+ defaultEyeFov = new FovHVHalves[config.defaultEyeParam.length];
+ for(int i=0; i<defaultEyeFov.length; i++) {
+ defaultEyeFov[i] = config.defaultEyeParam[i].fovhv;
+ }
+ }
+
+ @Override
+ public final StereoDeviceFactory getFactory() { return factory; }
+
+ @Override
+ public String toString() {
+ return "GenericStereoDevice["+config+", surfacePos "+surfacePos+"]";
+ }
+
+ public void setSurfacePosition(final int x, final int y) {
+ surfacePos.set(x, y);
+ }
+
+ @Override
+ public final void dispose() {
+ // NOP
+ }
+
+ @Override
+ public final PointImmutable getPosition() {
+ return surfacePos;
+ }
+
+ @Override
+ public final DimensionImmutable getSurfaceSize() {
+ return config.surfaceSizeInPixels;
+ }
+
+ @Override
+ public float[] getDefaultEyePositionOffset() {
+ return config.defaultEyeParam[0].positionOffset;
+ }
+
+ @Override
+ public final FovHVHalves[] getDefaultFOV() {
+ return defaultEyeFov;
+ }
+
+ @Override
+ public final boolean startSensors(final boolean start) {
+ if( start && !sensorsStarted ) {
+ if( startSensorsImpl(true) ) {
+ sensorsStarted = true;
+ return true;
+ } else {
+ sensorsStarted = false;
+ return false;
+ }
+ } else if( sensorsStarted ) {
+ if( startSensorsImpl(false) ) {
+ sensorsStarted = false;
+ return true;
+ } else {
+ sensorsStarted = true;
+ return false;
+ }
+ } else {
+ // No state change -> Success
+ return true;
+ }
+ }
+ private boolean startSensorsImpl(final boolean start) { return start; }
+
+ @Override
+ public boolean getSensorsStarted() { return sensorsStarted; }
+
+ @Override
+ public int[] getEyeRenderOrder() { return config.eyeRenderOrder; }
+
+ @Override
+ public int getSupportedDistortionBits() {
+ return config.supportedDistortionBits;
+ };
+
+ @Override
+ public int getRecommendedDistortionBits() {
+ return config.recommendedDistortionBits;
+ }
+
+ @Override
+ public int getMinimumDistortionBits() {
+ return config.minimumDistortionBits;
+ }
+
+ @Override
+ public final StereoDeviceRenderer createRenderer(final int distortionBits,
+ final int textureCount, final float[] eyePositionOffset,
+ final FovHVHalves[] eyeFov, final float pixelsPerDisplayPixel, final int textureUnit) {
+ final EyeParameter[] eyeParam = new EyeParameter[eyeFov.length];
+ for(int i=0; i<eyeParam.length; i++) {
+ final EyeParameter defaultEyeParam = config.defaultEyeParam[i];
+ eyeParam[i] = new EyeParameter(i, eyePositionOffset, eyeFov[i],
+ defaultEyeParam.distNoseToPupilX, defaultEyeParam.distMiddleToPupilY, defaultEyeParam.eyeReliefZ);
+ }
+
+ final boolean usePP = null != config.distortionMeshProducer && 0 != distortionBits; // use post-processing
+
+ final RectangleImmutable[] eyeViewports = new RectangleImmutable[eyeParam.length];
+ final DimensionImmutable eyeTextureSize = config.eyeTextureSize;
+ final DimensionImmutable totalTextureSize;
+ if( 1 < eyeParam.length ) {
+ // Stereo SBS
+ totalTextureSize = new Dimension(eyeTextureSize.getWidth()*2, eyeTextureSize.getHeight());
+
+ if( 1 == textureCount ) { // validated in ctor below!
+ eyeViewports[0] = new Rectangle(0, 0,
+ eyeTextureSize.getWidth(), eyeTextureSize.getHeight());
+
+ eyeViewports[1] = new Rectangle(eyeTextureSize.getWidth(), 0,
+ eyeTextureSize.getWidth(), eyeTextureSize.getHeight());
+ } else {
+ eyeViewports[0] = new Rectangle(0, 0, eyeTextureSize.getWidth(), eyeTextureSize.getHeight());
+ if( usePP ) {
+ eyeViewports[1] = eyeViewports[0];
+ } else {
+ eyeViewports[1] = new Rectangle(eyeTextureSize.getWidth(), 0,
+ eyeTextureSize.getWidth(), eyeTextureSize.getHeight());
+ }
+ }
+ } else {
+ // Mono
+ totalTextureSize = eyeTextureSize;
+ eyeViewports[0] = new Rectangle(0, 0, eyeTextureSize.getWidth(), eyeTextureSize.getHeight());
+ }
+ return new GenericStereoDeviceRenderer(this, distortionBits, textureCount, eyePositionOffset, eyeParam, pixelsPerDisplayPixel, textureUnit,
+ eyeTextureSize, totalTextureSize, eyeViewports);
+ }
+} \ No newline at end of file
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceFactory.java b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceFactory.java
new file mode 100644
index 000000000..f2fa74743
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceFactory.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2014 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.stereo;
+
+import com.jogamp.opengl.util.stereo.StereoDevice;
+import com.jogamp.opengl.util.stereo.StereoDeviceFactory;
+
+public class GenericStereoDeviceFactory extends StereoDeviceFactory {
+
+ public static boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public final StereoDevice createDevice(final int deviceIndex, final StereoDevice.Config config, final boolean verbose) {
+ return new GenericStereoDevice(this, deviceIndex, config);
+ }
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java
new file mode 100644
index 000000000..d957bd4e7
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java
@@ -0,0 +1,605 @@
+/**
+ * Copyright 2014 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.stereo;
+
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.media.nativewindow.util.Dimension;
+import javax.media.nativewindow.util.DimensionImmutable;
+import javax.media.nativewindow.util.RectangleImmutable;
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2ES2;
+import javax.media.opengl.GLArrayData;
+import javax.media.opengl.GLException;
+import javax.media.opengl.GLUniformData;
+
+import jogamp.common.os.PlatformPropsImpl;
+
+import com.jogamp.common.nio.Buffers;
+import com.jogamp.common.os.Platform;
+import com.jogamp.opengl.JoglVersion;
+import com.jogamp.opengl.util.GLArrayDataServer;
+import com.jogamp.opengl.util.glsl.ShaderCode;
+import com.jogamp.opengl.util.glsl.ShaderProgram;
+import com.jogamp.opengl.util.stereo.EyeParameter;
+import com.jogamp.opengl.util.stereo.EyePose;
+import com.jogamp.opengl.util.stereo.StereoDevice;
+import com.jogamp.opengl.util.stereo.StereoDeviceRenderer;
+import com.jogamp.opengl.util.stereo.StereoUtil;
+
+/**
+ * Generic Stereo Device Distortion and OpenGL Renderer Utility
+ */
+public class GenericStereoDeviceRenderer implements StereoDeviceRenderer {
+ private static final String shaderPrefix01 = "dist01";
+ private static final String shaderTimewarpSuffix = "_timewarp";
+ private static final String shaderChromaSuffix = "_chroma";
+ private static final String shaderPlainSuffix = "_plain";
+
+ public static class GenericEye implements StereoDeviceRenderer.Eye {
+ private final int eyeName;
+ private final int distortionBits;
+ private final int vertexCount;
+ private final int indexCount;
+ private final RectangleImmutable viewport;
+
+ private final GLUniformData eyeToSourceUVScale;
+ private final GLUniformData eyeToSourceUVOffset;
+ private final GLUniformData eyeRotationStart;
+ private final GLUniformData eyeRotationEnd;
+
+ /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */
+ private final GLArrayDataServer iVBO;
+ private final GLArrayData vboPos, vboParams, vboTexCoordsR, vboTexCoordsG, vboTexCoordsB;
+ private final GLArrayDataServer indices;
+
+ private final EyeParameter eyeParameter;
+
+ private final EyePose eyePose;
+
+ @Override
+ public final RectangleImmutable getViewport() { return viewport; }
+
+ @Override
+ public final EyeParameter getEyeParameter() { return eyeParameter; }
+
+ @Override
+ public final EyePose getLastEyePose() { return eyePose; }
+
+ private GenericEye(final GenericStereoDevice device, final int distortionBits,
+ final float[] eyePositionOffset, final EyeParameter eyeParam,
+ final DimensionImmutable textureSize, final RectangleImmutable eyeViewport) {
+ this.eyeName = eyeParam.number;
+ this.distortionBits = distortionBits;
+ this.viewport = eyeViewport;
+
+ final boolean usePP = null != device.config.distortionMeshProducer && 0 != distortionBits;
+
+ final boolean usesTimewarp = usePP && StereoUtil.usesTimewarpDistortion(distortionBits);
+ final FloatBuffer fstash = Buffers.newDirectFloatBuffer( 2 + 2 + ( usesTimewarp ? 16 + 16 : 0 ) ) ;
+
+ if( usePP ) {
+ eyeToSourceUVScale = new GLUniformData("svr_EyeToSourceUVScale", 2, Buffers.slice2Float(fstash, 0, 2));
+ eyeToSourceUVOffset = new GLUniformData("svr_EyeToSourceUVOffset", 2, Buffers.slice2Float(fstash, 2, 2));
+ } else {
+ eyeToSourceUVScale = null;
+ eyeToSourceUVOffset = null;
+ }
+
+ if( usesTimewarp ) {
+ eyeRotationStart = new GLUniformData("svr_EyeRotationStart", 4, 4, Buffers.slice2Float(fstash, 4, 16));
+ eyeRotationEnd = new GLUniformData("svr_EyeRotationEnd", 4, 4, Buffers.slice2Float(fstash, 20, 16));
+ } else {
+ eyeRotationStart = null;
+ eyeRotationEnd = null;
+ }
+
+ this.eyeParameter = eyeParam;
+
+ this.eyePose = new EyePose(eyeName);
+
+ updateEyePose(device); // 1st init
+
+ // Setup: eyeToSourceUVScale, eyeToSourceUVOffset
+ if( usePP ) {
+ final ScaleAndOffset2D textureScaleAndOffset = new ScaleAndOffset2D(eyeParam.fovhv, textureSize, eyeViewport);
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX."+eyeName+": eyeParam "+eyeParam);
+ System.err.println("XXX."+eyeName+": uvScaleOffset "+textureScaleAndOffset);
+ System.err.println("XXX."+eyeName+": textureSize "+textureSize);
+ System.err.println("XXX."+eyeName+": viewport "+eyeViewport);
+ }
+ final FloatBuffer eyeToSourceUVScaleFB = eyeToSourceUVScale.floatBufferValue();
+ eyeToSourceUVScaleFB.put(0, textureScaleAndOffset.scale[0]);
+ eyeToSourceUVScaleFB.put(1, textureScaleAndOffset.scale[1]);
+ final FloatBuffer eyeToSourceUVOffsetFB = eyeToSourceUVOffset.floatBufferValue();
+ eyeToSourceUVOffsetFB.put(0, textureScaleAndOffset.offset[0]);
+ eyeToSourceUVOffsetFB.put(1, textureScaleAndOffset.offset[1]);
+ } else {
+ vertexCount = 0;
+ indexCount = 0;
+ iVBO = null;
+ vboPos = null;
+ vboParams = null;
+ vboTexCoordsR = null;
+ vboTexCoordsG = null;
+ vboTexCoordsB = null;
+ indices = null;
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX."+eyeName+": "+this);
+ }
+ return;
+ }
+ final DistortionMesh meshData = device.config.distortionMeshProducer.create(eyeParam, distortionBits);
+ if( null == meshData ) {
+ throw new GLException("Failed to create meshData for eye "+eyeParam+", and "+StereoUtil.distortionBitsToString(distortionBits));
+ }
+
+ vertexCount = meshData.vertexCount;
+ indexCount = meshData.indexCount;
+
+ /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */
+ final boolean useChromatic = StereoUtil.usesChromaticDistortion(distortionBits);
+ final boolean useVignette = StereoUtil.usesVignetteDistortion(distortionBits);
+
+ final int compsPerElement = 2+2+2+( useChromatic ? 2+2 /* texCoordG + texCoordB */: 0 );
+ iVBO = GLArrayDataServer.createGLSLInterleaved(compsPerElement, GL.GL_FLOAT, false, vertexCount, GL.GL_STATIC_DRAW);
+ vboPos = iVBO.addGLSLSubArray("svr_Position", 2, GL.GL_ARRAY_BUFFER);
+ vboParams = iVBO.addGLSLSubArray("svr_Params", 2, GL.GL_ARRAY_BUFFER);
+ vboTexCoordsR = iVBO.addGLSLSubArray("svr_TexCoordR", 2, GL.GL_ARRAY_BUFFER);
+ if( useChromatic ) {
+ vboTexCoordsG = iVBO.addGLSLSubArray("svr_TexCoordG", 2, GL.GL_ARRAY_BUFFER);
+ vboTexCoordsB = iVBO.addGLSLSubArray("svr_TexCoordB", 2, GL.GL_ARRAY_BUFFER);
+ } else {
+ vboTexCoordsG = null;
+ vboTexCoordsB = null;
+ }
+ indices = GLArrayDataServer.createData(1, GL.GL_SHORT, indexCount, GL.GL_STATIC_DRAW, GL.GL_ELEMENT_ARRAY_BUFFER);
+
+ /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */
+ final FloatBuffer iVBOFB = (FloatBuffer)iVBO.getBuffer();
+
+ for ( int vertNum = 0; vertNum < vertexCount; vertNum++ ) {
+ final DistortionMesh.DistortionVertex v = meshData.vertices[vertNum];
+ int dataIdx = 0;
+
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": START VERTEX "+vertNum+" / "+vertexCount);
+ }
+ // pos
+ if( v.pos_size >= 2 ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": pos ["+v.data[dataIdx]+", "+v.data[dataIdx+1]+"]");
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ iVBOFB.put(v.data[dataIdx+1]);
+ } else {
+ iVBOFB.put(0f);
+ iVBOFB.put(0f);
+ }
+ dataIdx += v.pos_size;
+
+ // params
+ if( v.vignetteFactor_size >= 1 && useVignette ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": vignette "+v.data[dataIdx]);
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ } else {
+ iVBOFB.put(1.0f);
+ }
+ dataIdx += v.vignetteFactor_size;
+
+ if( v.timewarpFactor_size >= 1 ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": timewarp "+v.data[dataIdx]);
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ } else {
+ iVBOFB.put(1.0f);
+ }
+ dataIdx += v.timewarpFactor_size;
+
+ // texCoordR
+ if( v.texR_size >= 2 ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": texR ["+v.data[dataIdx]+", "+v.data[dataIdx+1]+"]");
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ iVBOFB.put(v.data[dataIdx+1]);
+ } else {
+ iVBOFB.put(1f);
+ iVBOFB.put(1f);
+ }
+ dataIdx += v.texR_size;
+
+ if( useChromatic ) {
+ // texCoordG
+ if( v.texG_size >= 2 ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": texG ["+v.data[dataIdx]+", "+v.data[dataIdx+1]+"]");
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ iVBOFB.put(v.data[dataIdx+1]);
+ } else {
+ iVBOFB.put(1f);
+ iVBOFB.put(1f);
+ }
+ dataIdx += v.texG_size;
+
+ // texCoordB
+ if( v.texB_size >= 2 ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": texB ["+v.data[dataIdx]+", "+v.data[dataIdx+1]+"]");
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ iVBOFB.put(v.data[dataIdx+1]);
+ } else {
+ iVBOFB.put(1f);
+ iVBOFB.put(1f);
+ }
+ dataIdx += v.texB_size;
+ } else {
+ dataIdx += v.texG_size;
+ dataIdx += v.texB_size;
+ }
+ }
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": iVBO "+iVBO);
+ }
+ {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": idx "+indices+", count "+indexCount);
+ for(int i=0; i< indexCount; i++) {
+ if( 0 == i % 16 ) {
+ System.err.printf("%n%5d: ", i);
+ }
+ System.err.printf("%5d, ", (int)meshData.indices[i]);
+ }
+ System.err.println();
+ }
+ final ShortBuffer out = (ShortBuffer) indices.getBuffer();
+ out.put(meshData.indices, 0, meshData.indexCount);
+ }
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX."+eyeName+": "+this);
+ }
+ }
+
+ private void linkData(final GL2ES2 gl, final ShaderProgram sp) {
+ if( null == iVBO ) return;
+
+ if( 0 > vboPos.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+vboPos);
+ }
+ if( 0 > vboParams.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+vboParams);
+ }
+ if( 0 > vboTexCoordsR.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+vboTexCoordsR);
+ }
+ if( StereoUtil.usesChromaticDistortion(distortionBits) ) {
+ if( 0 > vboTexCoordsG.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+vboTexCoordsG);
+ }
+ if( 0 > vboTexCoordsB.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+vboTexCoordsB);
+ }
+ }
+ if( 0 > eyeToSourceUVScale.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+eyeToSourceUVScale);
+ }
+ if( 0 > eyeToSourceUVOffset.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+eyeToSourceUVOffset);
+ }
+ if( StereoUtil.usesTimewarpDistortion(distortionBits) ) {
+ if( 0 > eyeRotationStart.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+eyeRotationStart);
+ }
+ if( 0 > eyeRotationEnd.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+eyeRotationEnd);
+ }
+ }
+ iVBO.seal(gl, true);
+ iVBO.enableBuffer(gl, false);
+ indices.seal(gl, true);
+ indices.enableBuffer(gl, false);
+ }
+
+ private void dispose(final GL2ES2 gl) {
+ if( null == iVBO ) return;
+ iVBO.destroy(gl);
+ indices.destroy(gl);
+ }
+ private void enableVBO(final GL2ES2 gl, final boolean enable) {
+ if( null == iVBO ) return;
+ iVBO.enableBuffer(gl, enable);
+ indices.bindBuffer(gl, enable); // keeps VBO binding if enable:=true
+ }
+
+ private void updateUniform(final GL2ES2 gl, final ShaderProgram sp) {
+ if( null == iVBO ) return;
+ gl.glUniform(eyeToSourceUVScale);
+ gl.glUniform(eyeToSourceUVOffset);
+ if( StereoUtil.usesTimewarpDistortion(distortionBits) ) {
+ gl.glUniform(eyeRotationStart);
+ gl.glUniform(eyeRotationEnd);
+ }
+ }
+
+ /**
+ * Updates {@link #ovrEyePose} and it's extracted
+ * {@link #eyeRenderPoseOrientation} and {@link #eyeRenderPosePosition}.
+ * @param hmdCtx used get the {@link #ovrEyePose} via {@link OVR#ovrHmd_GetEyePose(OvrHmdContext, int)}
+ */
+ private EyePose updateEyePose(final GenericStereoDevice hmdCtx) {
+ return eyePose;
+ }
+
+ @Override
+ public String toString() {
+ final String ppTxt = null == iVBO ? ", no post-processing" :
+ ", uvScale["+eyeToSourceUVScale.floatBufferValue().get(0)+", "+eyeToSourceUVScale.floatBufferValue().get(1)+
+ "], uvOffset["+eyeToSourceUVOffset.floatBufferValue().get(0)+", "+eyeToSourceUVOffset.floatBufferValue().get(1)+"]";
+
+ return "Eye["+eyeName+", viewport "+viewport+
+ ", "+eyeParameter+
+ ", vertices "+vertexCount+", indices "+indexCount+
+ ppTxt+
+ ", desc"+eyeParameter+", "+eyePose+"]";
+ }
+ }
+
+ private final GenericStereoDevice device;
+ private final GenericEye[] eyes;
+ private final int distortionBits;
+ private final int textureCount;
+ private final DimensionImmutable singleTextureSize;
+ private final DimensionImmutable totalTextureSize;
+ /** if texUnit0 is null: no post-processing */
+ private final GLUniformData texUnit0;
+
+ private ShaderProgram sp;
+ private long frameStart = 0;
+
+ @Override
+ public String toString() {
+ return "GenericStereo[distortion["+StereoUtil.distortionBitsToString(distortionBits)+
+ "], singleSize "+singleTextureSize+
+ ", sbsSize "+totalTextureSize+
+ ", texCount "+textureCount+", texUnit "+(null != texUnit0 ? texUnit0.intValue() : "n/a")+
+ ", "+PlatformPropsImpl.NEWLINE+" "+(0 < eyes.length ? eyes[0] : "none")+
+ ", "+PlatformPropsImpl.NEWLINE+" "+(1 < eyes.length ? eyes[1] : "none")+"]";
+ }
+
+
+ private static final DimensionImmutable zeroSize = new Dimension(0, 0);
+
+ /* pp */ GenericStereoDeviceRenderer(final GenericStereoDevice context, final int distortionBits,
+ final int textureCount, final float[] eyePositionOffset,
+ final EyeParameter[] eyeParam, final float pixelsPerDisplayPixel, final int textureUnit,
+ final DimensionImmutable singleTextureSize, final DimensionImmutable totalTextureSize,
+ final RectangleImmutable[] eyeViewports) {
+ this.device = context;
+ this.eyes = new GenericEye[eyeParam.length];
+ this.distortionBits = ( distortionBits | context.getMinimumDistortionBits() ) & context.getSupportedDistortionBits();
+ final boolean usePP = null != device.config.distortionMeshProducer && 0 != this.distortionBits;
+ final DimensionImmutable textureSize;
+
+ if( usePP ) {
+ if( 1 > textureCount || 2 < textureCount ) {
+ this.textureCount = 2;
+ } else {
+ this.textureCount = textureCount;
+ }
+ this.singleTextureSize = singleTextureSize;
+ this.totalTextureSize = totalTextureSize;
+ textureSize = 1 == textureCount ? totalTextureSize : singleTextureSize;
+ texUnit0 = new GLUniformData("svr_Texture0", textureUnit);
+ } else {
+ this.textureCount = 0;
+ this.singleTextureSize = zeroSize;
+ this.totalTextureSize = zeroSize;
+ textureSize = zeroSize;
+ texUnit0 = null;
+ }
+ for(int i=0; i<eyeParam.length; i++) {
+ eyes[i] = new GenericEye(context, this.distortionBits, eyePositionOffset, eyeParam[i], textureSize, eyeViewports[i]);
+ }
+ sp = null;
+ }
+
+ @Override
+ public StereoDevice getDevice() { return device; }
+
+ @Override
+ public final int getDistortionBits() { return distortionBits; }
+
+ @Override
+ public final boolean usesSideBySideStereo() { return true; }
+
+ @Override
+ public final DimensionImmutable getSingleSurfaceSize() { return singleTextureSize; }
+
+ @Override
+ public final DimensionImmutable getTotalSurfaceSize() { return totalTextureSize; }
+
+ @Override
+ public final int getTextureCount() { return textureCount; }
+
+ @Override
+ public final int getTextureUnit() { return ppAvailable() ? texUnit0.intValue() : 0; }
+
+ @Override
+ public final boolean ppAvailable() { return null != texUnit0; }
+
+ @Override
+ public final void init(final GL gl) {
+ if( StereoDevice.DEBUG ) {
+ System.err.println(JoglVersion.getGLInfo(gl, null).toString());
+ }
+ if( null != sp ) {
+ throw new IllegalStateException("Already initialized");
+ }
+ if( !ppAvailable() ) {
+ return;
+ }
+ final GL2ES2 gl2es2 = gl.getGL2ES2();
+
+ final String vertexShaderBasename;
+ final String fragmentShaderBasename;
+ {
+ final boolean usesTimewarp = StereoUtil.usesTimewarpDistortion(distortionBits);
+ final boolean usesChromatic = StereoUtil.usesChromaticDistortion(distortionBits);
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append(shaderPrefix01);
+ if( !usesChromatic && !usesTimewarp ) {
+ sb.append(shaderPlainSuffix);
+ } else if( usesChromatic && !usesTimewarp ) {
+ sb.append(shaderChromaSuffix);
+ } else if( usesTimewarp ) {
+ sb.append(shaderTimewarpSuffix);
+ if( usesChromatic ) {
+ sb.append(shaderChromaSuffix);
+ }
+ }
+ vertexShaderBasename = sb.toString();
+ sb.setLength(0);
+ sb.append(shaderPrefix01);
+ if( usesChromatic ) {
+ sb.append(shaderChromaSuffix);
+ } else {
+ sb.append(shaderPlainSuffix);
+ }
+ fragmentShaderBasename = sb.toString();
+ }
+ final ShaderCode vp0 = ShaderCode.create(gl2es2, GL2ES2.GL_VERTEX_SHADER, GenericStereoDeviceRenderer.class, "shader",
+ "shader/bin", vertexShaderBasename, true);
+ final ShaderCode fp0 = ShaderCode.create(gl2es2, GL2ES2.GL_FRAGMENT_SHADER, GenericStereoDeviceRenderer.class, "shader",
+ "shader/bin", fragmentShaderBasename, true);
+ vp0.defaultShaderCustomization(gl2es2, true, true);
+ fp0.defaultShaderCustomization(gl2es2, true, true);
+
+ sp = new ShaderProgram();
+ sp.add(gl2es2, vp0, System.err);
+ sp.add(gl2es2, fp0, System.err);
+ if(!sp.link(gl2es2, System.err)) {
+ throw new GLException("could not link program: "+sp);
+ }
+ sp.useProgram(gl2es2, true);
+ if( 0 > texUnit0.setLocation(gl2es2, sp.program()) ) {
+ throw new GLException("Couldn't locate "+texUnit0);
+ }
+ for(int i=0; i<eyes.length; i++) {
+ eyes[i].linkData(gl2es2, sp);
+ }
+ sp.useProgram(gl2es2, false);
+ }
+
+ @Override
+ public final void dispose(final GL gl) {
+ final GL2ES2 gl2es2 = gl.getGL2ES2();
+ if( null != sp ) {
+ sp.useProgram(gl2es2, false);
+ }
+ for(int i=0; i<eyes.length; i++) {
+ eyes[i].dispose(gl2es2);
+ }
+ if( null != sp ) {
+ sp.destroy(gl2es2);
+ }
+ }
+
+ @Override
+ public final Eye getEye(final int eyeNum) {
+ return eyes[eyeNum];
+ }
+
+ @Override
+ public final EyePose updateEyePose(final int eyeNum) {
+ return eyes[eyeNum].updateEyePose(device);
+ }
+
+ @Override
+ public final void beginFrame(final GL gl) {
+ frameStart = Platform.currentTimeMillis();
+ }
+
+ @Override
+ public final void endFrame(final GL gl) {
+ if( 0 == frameStart ) {
+ throw new IllegalStateException("beginFrame not called");
+ }
+ frameStart = 0;
+ }
+
+ @Override
+ public final void ppBegin(final GL gl) {
+ if( null == sp ) {
+ throw new IllegalStateException("Not initialized");
+ }
+ if( 0 == frameStart ) {
+ throw new IllegalStateException("beginFrame not called");
+ }
+ final GL2ES2 gl2es2 = gl.getGL2ES2();
+
+ gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ gl.glClear(GL.GL_COLOR_BUFFER_BIT);
+ gl.glActiveTexture(GL.GL_TEXTURE0 + getTextureUnit());
+
+ gl2es2.glDisable(GL.GL_CULL_FACE);
+ gl2es2.glDisable(GL.GL_DEPTH_TEST);
+ gl2es2.glDisable(GL.GL_BLEND);
+
+ if( !gl2es2.isGLcore() ) {
+ gl2es2.glEnable(GL.GL_TEXTURE_2D);
+ }
+
+ sp.useProgram(gl2es2, true);
+
+ gl2es2.glUniform(texUnit0);
+ }
+
+ @Override
+ public final void ppOneEye(final GL gl, final int eyeNum) {
+ final GenericEye eye = eyes[eyeNum];
+ final GL2ES2 gl2es2 = gl.getGL2ES2();
+
+ eye.updateUniform(gl2es2, sp);
+ eye.enableVBO(gl2es2, true);
+ gl2es2.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0);
+ eyes[eyeNum].enableVBO(gl2es2, false);
+ }
+
+ @Override
+ public final void ppEnd(final GL gl) {
+ sp.useProgram(gl.getGL2ES2(), false);
+ }
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/ScaleAndOffset2D.java b/src/jogl/classes/jogamp/opengl/util/stereo/ScaleAndOffset2D.java
new file mode 100644
index 000000000..ce154e03e
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/ScaleAndOffset2D.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright 2014 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.util.stereo;
+
+import javax.media.nativewindow.util.DimensionImmutable;
+import javax.media.nativewindow.util.RectangleImmutable;
+
+import com.jogamp.opengl.math.FovHVHalves;
+import com.jogamp.opengl.math.VectorUtil;
+
+/**
+ * 2D scale and offset NDC class,
+ * providing conversion from {@link FovHVHalves} in tangent to NDC space.
+ * <p>
+ * See <a href="https://www.opengl.org/wiki/Compute_eye_space_from_window_space">OpenGL.org: Compute eye space from window space</a>
+ * </p>
+ */
+public final class ScaleAndOffset2D {
+ /** Scale for x- and y-component. */
+ final float[] scale;
+ /** Offset for x- and y-component. */
+ final float[] offset;
+
+ private static final float[] vec2Half = new float[] { 0.5f, 0.5f };
+
+ public String toString() {
+ return "[offset "+offset[0]+" / "+offset[1]+", scale "+scale[0]+" x "+scale[1]+"]";
+ }
+
+ public ScaleAndOffset2D(final float[] scale, final float[] offset) {
+ this.scale = scale;
+ this.offset = offset;
+ }
+
+ /**
+ * Create the <i>Normalized Device Coordinate Space</i> (NDC) [-1,+1] instance
+ * from the given <code>fovHVHalves</code>.
+ */
+ public ScaleAndOffset2D(final FovHVHalves fovHVHalves) {
+ final FovHVHalves tanFovHVHalves = fovHVHalves.toTangents();
+ final float projXScale = 2.0f / ( tanFovHVHalves.left+ tanFovHVHalves.right);
+ final float projYScale = 2.0f / ( tanFovHVHalves.top + tanFovHVHalves.bottom );
+ final float projXOffset = ( tanFovHVHalves.left - tanFovHVHalves.right ) * projXScale * 0.5f;
+ final float projYOffset = ( tanFovHVHalves.top - tanFovHVHalves.bottom ) * projYScale * 0.5f;
+
+ this.scale = new float[] { projXScale, projYScale };
+ this.offset = new float[] { projXOffset, projYOffset };
+ }
+
+ /**
+ * Create the <i>Normalized Device Coordinate Space</i> (NDC) [-1,+1] instance
+ * from the given <code>fovHVHalves</code>, for the subsection of the <code>render-viewport</code> within the <code>rendertarget-size</code>.
+ */
+ public ScaleAndOffset2D(final FovHVHalves fovHVHalves, final DimensionImmutable rendertargetSize, final RectangleImmutable renderViewport) {
+ final ScaleAndOffset2D eyeToSourceNDC = new ScaleAndOffset2D(fovHVHalves);
+ final float[] vec2Tmp1 = new float[2];
+ final float[] vec2Tmp2 = new float[2];
+ final float[] scale = VectorUtil.scaleVec2(vec2Tmp1, eyeToSourceNDC.scale, 0.5f);
+ final float[] offset = VectorUtil.addVec2(vec2Tmp2, VectorUtil.scaleVec2(vec2Tmp2, eyeToSourceNDC.offset, 0.5f), vec2Half);
+
+ final float[] scale2 = new float[] { (float)renderViewport.getWidth() / (float)rendertargetSize.getWidth(),
+ (float)renderViewport.getHeight() / (float)rendertargetSize.getHeight() };
+
+ final float[] offset2 = new float[] { (float)renderViewport.getX() / (float)rendertargetSize.getWidth(),
+ (float)renderViewport.getY() / (float)rendertargetSize.getHeight() };
+
+ VectorUtil.scaleVec2(scale, scale, scale2);
+ VectorUtil.addVec2(offset, VectorUtil.scaleVec2(offset, offset, scale2), offset2);
+
+ this.scale = scale;
+ this.offset = offset;
+ }
+
+ /**
+ * Return the <i>tangent FOV space</i> of this <i>eye to source NDC</i> instance.
+ */
+ public final float[] convertToTanFovSpace(final float[] rendertargetNDC) {
+ final float[] vec2Tmp1 = new float[2];
+ return VectorUtil.divVec2(vec2Tmp1, VectorUtil.subVec2(vec2Tmp1, rendertargetNDC, this.offset), this.scale);
+ }
+
+} \ No newline at end of file
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_chroma.fp b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_chroma.fp
new file mode 100644
index 000000000..4ac404729
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_chroma.fp
@@ -0,0 +1,26 @@
+//Copyright 2014 JogAmp Community. All rights reserved.
+
+#if __VERSION__ >= 130
+ #define varying in
+ out vec4 svr_FragColor;
+ #define texture2D texture
+#else
+ #define svr_FragColor gl_FragColor
+#endif
+
+uniform sampler2D svr_Texture0;
+
+varying vec3 svv_Fade;
+varying vec2 svv_TexCoordR;
+varying vec2 svv_TexCoordG;
+varying vec2 svv_TexCoordB;
+
+void main (void)
+{
+ // 3 samples for fixing chromatic aberrations
+ vec3 color = vec3(texture2D(svr_Texture0, svv_TexCoordR).r,
+ texture2D(svr_Texture0, svv_TexCoordG).g,
+ texture2D(svr_Texture0, svv_TexCoordB).b);
+ svr_FragColor = vec4(svv_Fade * color, 1.0); // include vignetteFade
+}
+
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_chroma.vp b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_chroma.vp
new file mode 100644
index 000000000..d4ab585d5
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_chroma.vp
@@ -0,0 +1,33 @@
+//Copyright 2014 JogAmp Community. All rights reserved.
+
+#if __VERSION__ >= 130
+ #define attribute in
+ #define varying out
+#endif
+
+uniform vec2 svr_EyeToSourceUVScale;
+uniform vec2 svr_EyeToSourceUVOffset;
+
+attribute vec2 svr_Position;
+attribute vec2 svr_Params;
+attribute vec2 svr_TexCoordR;
+attribute vec2 svr_TexCoordG;
+attribute vec2 svr_TexCoordB;
+
+varying vec3 svv_Fade;
+varying vec2 svv_TexCoordR;
+varying vec2 svv_TexCoordG;
+varying vec2 svv_TexCoordB;
+
+void main(void)
+{
+ gl_Position = vec4(svr_Position.xy, 0.5, 1.0);
+ svv_Fade = vec3(svr_Params.r); // vignetteFade
+
+ svv_TexCoordR = svr_TexCoordR * svr_EyeToSourceUVScale + svr_EyeToSourceUVOffset;
+ svv_TexCoordR.y = 1.0-svv_TexCoordR.y;
+ svv_TexCoordG = svr_TexCoordG * svr_EyeToSourceUVScale + svr_EyeToSourceUVOffset;
+ svv_TexCoordG.y = 1.0-svv_TexCoordG.y;
+ svv_TexCoordB = svr_TexCoordB * svr_EyeToSourceUVScale + svr_EyeToSourceUVOffset;
+ svv_TexCoordB.y = 1.0-svv_TexCoordB.y;
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_plain.fp b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_plain.fp
new file mode 100644
index 000000000..2df890648
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_plain.fp
@@ -0,0 +1,22 @@
+//Copyright 2014 JogAmp Community. All rights reserved.
+
+#if __VERSION__ >= 130
+ #define varying in
+ out vec4 svr_FragColor;
+ #define texture2D texture
+#else
+ #define svr_FragColor gl_FragColor
+#endif
+
+uniform sampler2D svr_Texture0;
+
+varying vec3 svv_Fade;
+varying vec2 svv_TexCoordR;
+
+void main (void)
+{
+ // 3 samples for fixing chromatic aberrations
+ vec3 color = texture2D(svr_Texture0, svv_TexCoordR).rgb;
+ svr_FragColor = vec4(svv_Fade * color, 1.0); // include vignetteFade
+}
+
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_plain.vp b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_plain.vp
new file mode 100644
index 000000000..335d3f0f6
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_plain.vp
@@ -0,0 +1,27 @@
+//Copyright 2014 JogAmp Community. All rights reserved.
+
+#if __VERSION__ >= 130
+ #define attribute in
+ #define varying out
+#endif
+
+uniform vec2 svr_EyeToSourceUVScale;
+uniform vec2 svr_EyeToSourceUVOffset;
+
+attribute vec2 svr_Position;
+attribute vec2 svr_Params;
+attribute vec2 svr_TexCoordR;
+
+varying vec3 svv_Fade;
+varying vec2 svv_TexCoordR;
+
+void main(void)
+{
+ gl_Position = vec4(svr_Position.xy, 0.5, 1.0);
+ svv_Fade = vec3(svr_Params.r); // vignetteFade
+
+ // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic aberration and distortion).
+ // Scale them into the correct [0-1],[0-1] UV lookup space (depending on eye)
+ svv_TexCoordR = svr_TexCoordR * svr_EyeToSourceUVScale + svr_EyeToSourceUVOffset;
+ svv_TexCoordR.y = 1.0-svv_TexCoordR.y;
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_timewarp.vp b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_timewarp.vp
new file mode 100644
index 000000000..c4461ec3e
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_timewarp.vp
@@ -0,0 +1,44 @@
+//Copyright 2014 JogAmp Community. All rights reserved.
+
+#if __VERSION__ >= 130
+ #define attribute in
+ #define varying out
+#endif
+
+uniform vec2 svr_EyeToSourceUVScale;
+uniform vec2 svr_EyeToSourceUVOffset;
+uniform mat4 svr_EyeRotationStart;
+uniform mat4 svr_EyeRotationEnd;
+
+attribute vec2 svr_Position;
+attribute vec2 svr_Params;
+attribute vec2 svr_TexCoordR;
+
+varying vec3 svv_Fade;
+varying vec2 svv_TexCoordR;
+
+void main(void)
+{
+ gl_Position = vec4(svr_Position.xy, 0.0, 1.0);
+ svv_Fade = vec3(svr_Params.r); // vignetteFade
+
+ // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic aberration and distortion).
+ // These are now "real world" vectors in direction (x,y,1) relative to the eye of the HMD.
+ vec3 TanEyeAngle = vec3 ( svr_TexCoordR, 1.0 );
+
+ // Accurate time warp lerp vs. faster
+ // Apply the two 3x3 timewarp rotations to these vectors.
+ vec3 TransformedStart = (svr_EyeRotationStart * vec4(TanEyeAngle, 0)).xyz;
+ vec3 TransformedEnd = (svr_EyeRotationEnd * vec4(TanEyeAngle, 0)).xyz;
+ // And blend between them.
+ vec3 Transformed = mix ( TransformedStart, TransformedEnd, svr_Params.g /* timewarpLerpFactor */ );
+
+ // Project them back onto the Z=1 plane of the rendered images.
+ float RecipZ = 1.0 / Transformed.z;
+ vec2 Flattened = vec2 ( Transformed.x * RecipZ, Transformed.y * RecipZ );
+
+ // These are now still in TanEyeAngle space.
+ // Scale them into the correct [0-1],[0-1] UV lookup space (depending on eye)
+ svv_TexCoordR = Flattened * svr_EyeToSourceUVScale + svr_EyeToSourceUVOffset;
+ svv_TexCoordR.y = 1.0-svv_TexCoordR.y;
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_timewarp_chroma.vp b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_timewarp_chroma.vp
new file mode 100644
index 000000000..c08ed3113
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/shader/dist01_timewarp_chroma.vp
@@ -0,0 +1,65 @@
+//Copyright 2014 JogAmp Community. All rights reserved.
+
+#if __VERSION__ >= 130
+ #define attribute in
+ #define varying out
+#endif
+
+uniform vec2 svr_EyeToSourceUVScale;
+uniform vec2 svr_EyeToSourceUVOffset;
+uniform mat4 svr_EyeRotationStart;
+uniform mat4 svr_EyeRotationEnd;
+
+attribute vec2 svr_Position;
+attribute vec2 svr_Params;
+attribute vec2 svr_TexCoordR;
+attribute vec2 svr_TexCoordG;
+attribute vec2 svr_TexCoordB;
+
+varying vec3 svv_Fade;
+varying vec2 svv_TexCoordR;
+varying vec2 svv_TexCoordG;
+varying vec2 svv_TexCoordB;
+
+void main(void)
+{
+ gl_Position = vec4(svr_Position.xy, 0.0, 1.0);
+ svv_Fade = vec3(svr_Params.r); // vignetteFade
+
+ // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic aberration and distortion).
+ // These are now "real world" vectors in direction (x,y,1) relative to the eye of the HMD.
+ vec3 TanEyeAngleR = vec3 ( svr_TexCoordR, 1.0 );
+ vec3 TanEyeAngleG = vec3 ( svr_TexCoordG, 1.0 );
+ vec3 TanEyeAngleB = vec3 ( svr_TexCoordB, 1.0 );
+
+ // Accurate time warp lerp vs. faster
+ // Apply the two 3x3 timewarp rotations to these vectors.
+ vec3 TransformedRStart = (svr_EyeRotationStart * vec4(TanEyeAngleR, 0)).xyz;
+ vec3 TransformedGStart = (svr_EyeRotationStart * vec4(TanEyeAngleG, 0)).xyz;
+ vec3 TransformedBStart = (svr_EyeRotationStart * vec4(TanEyeAngleB, 0)).xyz;
+ vec3 TransformedREnd = (svr_EyeRotationEnd * vec4(TanEyeAngleR, 0)).xyz;
+ vec3 TransformedGEnd = (svr_EyeRotationEnd * vec4(TanEyeAngleG, 0)).xyz;
+ vec3 TransformedBEnd = (svr_EyeRotationEnd * vec4(TanEyeAngleB, 0)).xyz;
+
+ // And blend between them.
+ vec3 TransformedR = mix ( TransformedRStart, TransformedREnd, svr_Params.g /* timewarpLerpFactor */ );
+ vec3 TransformedG = mix ( TransformedGStart, TransformedGEnd, svr_Params.g /* timewarpLerpFactor */ );
+ vec3 TransformedB = mix ( TransformedBStart, TransformedBEnd, svr_Params.g /* timewarpLerpFactor */ );
+
+ // Project them back onto the Z=1 plane of the rendered images.
+ float RecipZR = 1.0 / TransformedR.z;
+ float RecipZG = 1.0 / TransformedG.z;
+ float RecipZB = 1.0 / TransformedB.z;
+ vec2 FlattenedR = vec2 ( TransformedR.x * RecipZR, TransformedR.y * RecipZR );
+ vec2 FlattenedG = vec2 ( TransformedG.x * RecipZG, TransformedG.y * RecipZG );
+ vec2 FlattenedB = vec2 ( TransformedB.x * RecipZB, TransformedB.y * RecipZB );
+
+ // These are now still in TanEyeAngle space.
+ // Scale them into the correct [0-1],[0-1] UV lookup space (depending on eye)
+ svv_TexCoordR = FlattenedR * svr_EyeToSourceUVScale + svr_EyeToSourceUVOffset;
+ svv_TexCoordR.y = 1.0-svv_TexCoordR.y;
+ svv_TexCoordG = FlattenedG * svr_EyeToSourceUVScale + svr_EyeToSourceUVOffset;
+ svv_TexCoordG.y = 1.0-svv_TexCoordG.y;
+ svv_TexCoordB = FlattenedB * svr_EyeToSourceUVScale + svr_EyeToSourceUVOffset;
+ svv_TexCoordB.y = 1.0-svv_TexCoordB.y;
+}