aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/jogamp/opengl/util
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2014-02-23 14:51:06 +0100
committerSven Gothel <[email protected]>2014-02-23 14:51:06 +0100
commit3352601e0860584509adf2b76f993d03893ded4b (patch)
tree974fccc8c0eb2f5ad9d4ffd741dfc35869ed67b5 /src/jogl/classes/jogamp/opengl/util
parentf51933f0ebe9ae030c26c066e59a728ce08b8559 (diff)
parentc67de337a8aaf52e36104c3f13e273aa19d21f1f (diff)
Merge branch 'master' into stash_glyphcache
Conflicts: make/scripts/tests.sh src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java src/jogl/classes/com/jogamp/graph/curve/Region.java src/jogl/classes/com/jogamp/graph/curve/opengl/GLRegion.java src/jogl/classes/com/jogamp/graph/curve/opengl/RegionRenderer.java src/jogl/classes/com/jogamp/graph/curve/opengl/Renderer.java src/jogl/classes/com/jogamp/graph/curve/opengl/TextRenderer.java src/jogl/classes/com/jogamp/graph/font/Font.java src/jogl/classes/com/jogamp/opengl/math/VectorUtil.java src/jogl/classes/jogamp/graph/curve/text/GlyphShape.java src/jogl/classes/jogamp/graph/curve/text/GlyphString.java src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java
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.java61
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLDataArrayHandler.java52
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandler.java58
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandlerFlat.java45
-rw-r--r--src/jogl/classes/jogamp/opengl/util/GLVBOArrayHandler.java71
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java77
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java1598
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java229
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java129
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java137
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java439
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java1029
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGNatives.java281
-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.java130
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandler.java100
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerFlat.java70
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerInterleaved.java66
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/GLSLTextureRaster.java195
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java271
-rw-r--r--src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncPipeline.java1224
-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.java1517
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/FilterType.java67
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/FilterWriteStrategy.java4
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/ImageInfo.java56
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/ImageLine.java372
-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/PngHelperInternal.java (renamed from src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java)147
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkInputStream.java72
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java10
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngReader.java1416
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngWriter.java709
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java4
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngjExceptionInternal.java24
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/PngjUnsupportedException.java3
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java15
-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.java107
-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.java250
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java50
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java58
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java30
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java32
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java35
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java29
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java20
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIHDR.java41
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java36
-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.java30
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPLTE.java23
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSBIT.java42
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java55
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSRGB.java28
-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.java32
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTIME.java40
-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.java10
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java21
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java28
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java214
-rw-r--r--src/jogl/classes/jogamp/opengl/util/pngj/package.html5
93 files changed, 11539 insertions, 3476 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..89e01edd8 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;
+ super(ad);
}
-
+
+ @Override
public final void setSubArrayVBOName(int vboName) {
for(int i=0; i<subArrays.size(); i++) {
subArrays.get(i).getData().setVBOName(vboName);
- }
+ }
}
-
+
+ @Override
public final void addSubHandler(GLArrayHandlerFlat handler) {
subArrays.add(handler);
}
- private final void syncSubData(GL gl, boolean enable, boolean force, Object ext) {
+ private final void syncSubData(GL gl, 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(GL gl, boolean enable, 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..8a587980d 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;
+ super(ad);
}
+ @Override
public final void setSubArrayVBOName(int vboName) {
throw new UnsupportedOperationException();
}
-
+
+ @Override
public final void addSubHandler(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(GL gl, boolean enable, 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..7f7a99a2d 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 class GLFixedArrayHandler extends GLVBOArrayHandler {
public GLFixedArrayHandler(GLArrayDataEditable ad) {
- this.ad = ad;
+ super(ad);
}
-
+
+ @Override
public final void setSubArrayVBOName(int vboName) {
throw new UnsupportedOperationException();
}
-
+
+ @Override
public final void addSubHandler(GLArrayHandlerFlat handler) {
throw new UnsupportedOperationException();
}
-
- public final void syncData(GL gl, boolean enable, Object ext) {
+
+ @Override
+ public final void enableState(GL gl, boolean enable, 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..acec0510f 100644
--- a/src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandlerFlat.java
+++ b/src/jogl/classes/jogamp/opengl/util/GLFixedArrayHandlerFlat.java
@@ -35,7 +35,7 @@ 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 {
@@ -45,36 +45,37 @@ public class GLFixedArrayHandlerFlat implements GLArrayHandlerFlat {
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(GL gl, 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);
}
}
+ @Override
public final void enableState(GL gl, boolean enable, 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..5198cacfa
--- /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(GLArrayDataEditable ad) {
+ this.ad = ad;
+ }
+
+ @Override
+ public final boolean bindBuffer(GL gl, 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/EGLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java
index 31f13297b..c39b78bb8 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,43 +44,40 @@ 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){
this.id = id;
}
- }
-
+ }
+
public static class EGLTextureFrame extends TextureSequence.TextureFrame {
-
+
public EGLTextureFrame(Buffer clientBuffer, Texture t, long khrImage, 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) {
super();
this.texType = texType;
@@ -88,36 +85,36 @@ public abstract class EGLMediaPlayerImpl extends GLMediaPlayerImpl {
}
@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(GL gl, int texName) {
+ final Texture texture = super.createTexImageImpl(gl, texName, width, height);
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);
// create EGLImage from texture
clientBuffer = null; // FIXME
nioTmp.put(0, EGL.EGL_NONE);
- image = eglExt.eglCreateImageKHR( eglDrawable.getDisplay(), eglCtx.getHandle(),
+ image = eglExt.eglCreateImageKHR( eglDrawable.getNativeSurface().getDisplayHandle(), eglCtx.getHandle(),
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];
+ 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.getDisplay(), 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(GL gl, 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.getDisplay(), eglTex.getImage());
+ eglExt.eglDestroyImageKHR(eglDrawable.getNativeSurface().getDisplayHandle(), eglTex.getImage());
}
if(0!=eglTex.getSync()) {
- eglExt.eglDestroySyncKHR(eglDrawable.getDisplay(), 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 d3d45e692..7cea51dc8 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,331 +28,670 @@
package jogamp.opengl.util.av;
import java.io.IOException;
-import java.net.URLConnection;
+import java.net.URI;
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.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.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);
protected static final String unknown = "unknown";
- protected State state;
+ protected volatile State state;
+ private final Object stateLock = new Object();
+
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. */
+
+ /** User requested URI stream location. */
+ protected 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 String cameraPath = null;
+ /** Optional camera properties, see {@link #cameraPath}. */
+ protected Map<String, String> cameraProps = null;
+
+ protected volatile float playSpeed = 1.0f;
+ protected float audioVolume = 1.0f;
+
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ protected int vid = GLMediaPlayer.STREAM_ID_AUTO;
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ protected int aid = GLMediaPlayer.STREAM_ID_AUTO;
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
protected int width = 0;
- /** Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
protected int height = 0;
- /** Video fps. Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
+ /** Video avg. fps. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
protected float fps = 0;
- /** Stream bps. Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
+ /** Video avg. frame duration in ms. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ protected float frame_duration = 0f;
+ /** Stream bps. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
protected int bps_stream = 0;
- /** Video bps. Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
+ /** Video bps. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
protected int bps_video = 0;
- /** Audio bps. Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
+ /** Audio bps. Shall be set by the {@link #initStreamImpl(int, 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. */
+ /** In frames. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ protected int videoFrames = 0;
+ /** In frames. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
+ protected int audioFrames = 0;
+ /** In ms. Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
protected int duration = 0;
- /** Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
+ /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */
protected String acodec = unknown;
- /** Shall be set by the {@link #initGLStreamImpl(GL, int[])} method implementation. */
+ /** Shall be set by the {@link #initStreamImpl(int, 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>();
+
+ protected volatile int decodedFrameCount = 0;
+ protected int presentedFrameCount = 0;
+ protected int displayedFrameCount = 0;
+ protected 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;
+
+ /** 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;
+
+ protected TextureFrame[] videoFramesOrig = null;
+ protected Ringbuffer<TextureFrame> videoFramesFree = null;
+ protected Ringbuffer<TextureFrame> videoFramesDecoded = null;
+ protected volatile TextureFrame lastFrame = null;
+ /**
+ * @see #isGLOriented()
+ */
+ protected 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.textureType = GL.GL_UNSIGNED_BYTE;
+ this.textureInternalFormat = GL.GL_RGBA;
+ this.textureType = GL.GL_UNSIGNED_BYTE;
this.texUnit = 0;
this.state = State.Uninitialized;
}
@Override
- public void setTextureUnit(int u) { texUnit = u; }
-
+ public final void setTextureUnit(int u) { texUnit = u; }
+
@Override
- public int getTextureUnit() { return texUnit; }
-
- protected final void setTextureCount(int textureCount) {
- this.textureCount=textureCount;
- }
+ public final int getTextureUnit() { return texUnit; }
+
+ @Override
+ public final int getTextureTarget() { return textureTarget; }
+
@Override
public final int getTextureCount() { return textureCount; }
-
+
protected final void setTextureTarget(int target) { textureTarget=target; }
- protected final void setTextureFormat(int f) { textureFormat=f; }
+ protected final void setTextureFormat(int internalFormat, int format) {
+ textureInternalFormat=internalFormat;
+ textureFormat=format;
+ }
protected final void setTextureType(int t) { textureType=t; }
+ @Override
public final void setTextureMinMagFilter(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(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 GL2.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);
- }
+ 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 getDecodedFrameCount() { return decodedFrameCount; }
+
@Override
- public final synchronized void setPlaySpeed(float rate) {
- if(State.Uninitialized != state && setPlaySpeedImpl(rate)) {
- playSpeed = rate;
+ 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; }
+
+ @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(boolean flush) {
+ return pauseImpl(flush, 0);
+ }
+ private final State pauseImpl(boolean flush, int event_mask) {
+ synchronized( stateLock ) {
+ final State preState = state;
+ if( State.Playing == state ) {
+ event_mask = addStateEventMask(event_mask, GLMediaPlayer.State.Paused);
+ state = 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(GL gl) {
+ return destroyImpl(gl, 0);
+ }
+ private final State destroyImpl(GL gl, 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;
+ state = 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();
+ }
+ state = _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(float rate) {
+ if( null != audioSink ) {
+ audioSinkPlaySpeedSet = audioSink.setPlaySpeed(rate);
+ }
+ // 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;
+ }
+ /**
+ * 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;
}
- return state;
}
-
/**
- * 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#setVolume(float)} is not sufficient!
+ */
+ protected boolean setAudioVolumeImpl(float v) {
+ if( null != audioSink ) {
+ return audioSink.setVolume(v);
+ }
+ // still true, even if audioSink rejects command ..
+ return true;
+ }
+
+ @Override
+ public final void initStream(URI streamLoc, final int vid, final int aid, 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 String streamLocScheme = streamLoc.getScheme();
+ if( null != streamLocScheme && streamLocScheme.equals(CameraInputScheme) ) {
+ final String rawPath = streamLoc.getRawPath();
+ 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;
+ if ( this.streamLoc != null ) {
+ new Thread() {
+ public void run() {
+ try {
+ // StreamWorker may be used, see API-doc of StreamWorker
+ initStreamImpl(vid, aid);
+ } catch (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;
}
-
- 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.");
+
+ @Override
+ public final void initGL(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 (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(int desiredTextureCount) {
+ return desiredTextureCount < TEXTURE_COUNT_MIN ? TEXTURE_COUNT_MIN : desiredTextureCount;
+ }
+
+ protected TextureFrame[] createTexFrames(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 abstract TextureFrame createTexImage(GL gl, int texName);
+
+ protected final Texture createTexImageImpl(GL gl, int texName, int tWidth, 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));
}
}
@@ -361,9 +700,9 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
gl.glTexImage2D(
textureTarget, // target
0, // level
- GL.GL_RGBA, // internal format
- tWidth, // width
- tHeight, // height
+ textureInternalFormat, // internal format
+ tWidth, // width
+ tHeight, // height
0, // border
textureFormat,
textureType,
@@ -371,55 +710,691 @@ 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,
+
+ return com.jogamp.opengl.util.texture.TextureIO.newTexture(
+ texName, 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);
+ !isInGLOrientation);
+ }
+
+ protected void destroyTexFrame(GL gl, TextureFrame frame) {
+ frame.getTexture().destroy(gl);
+ }
+
+ @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(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");
+ }
+ }
+ }
+ }
+
+ protected TextureFrame cachedFrame = null;
+ protected long lastTimeMillis = 0;
+
+ private final boolean[] stGotVFrame = { false };
+
+ @Override
+ public final TextureFrame getNextTexture(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 (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ displayedFrameCount++;
+ return lastFrame;
+ }
+ }
+ protected void preNextTextureImpl(GL gl) {}
+ protected void postNextTextureImpl(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, 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(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(TextureFrame frame, long currentTimeMillis) {
+ decodedFrameCount++;
+ if( 0 == frame.getDuration() ) { // patch frame duration if not set already
+ frame.setDuration( (int) frame_duration );
+ }
+ synchronized(eventListenersLock) {
+ for(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 (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private void makeCurrent(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 (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(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 ) {
+ try {
+ this.wait(); // wait until paused
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ public final synchronized void doResume() {
+ if( isRunning && !isActive ) {
+ shallPause = false;
+ if( Thread.currentThread() != this ) {
+ while( !isActive ) {
+ this.notifyAll(); // wake-up pause-block
+ try {
+ this.wait(); // wait until resumed
+ } catch (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 (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 (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 (InterruptedException e) {
+ isBlocked = false;
+ if( !shallStop && !shallPause ) {
+ streamErr = new StreamException("InterruptedException while decoding: "+GLMediaPlayerImpl.this.toString(), e);
+ }
+ } catch (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, 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(int event_mask) {
+ if( 0 != event_mask ) {
+ final long now = Platform.currentTimeMillis();
+ synchronized(eventListenersLock) {
+ for(Iterator<GLMediaEventListener> i = eventListeners.iterator(); i.hasNext(); ) {
+ i.next().attributesChanged(this, event_mask, now);
+ }
}
}
- 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) {
+ protected final void changeState(int event_mask, State newState) {
+ event_mask = addStateEventMask(event_mask, newState);
+ if( 0 != event_mask ) {
+ state = newState;
+ attributesUpdated( event_mask );
+ }
+ }
+
+ protected final void updateAttributes(int vid, int aid, int width, int height, int bps_stream,
+ int bps_video, int bps_audio, float fps,
+ int videoFrames, int audioFrames, int duration, String vcodec, String acodec) {
int event_mask = 0;
+ final boolean wasUninitialized = state == State.Uninitialized;
+
+ if( wasUninitialized ) {
+ event_mask |= GLMediaEventListener.EVENT_CHANGE_INIT;
+ state = 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;
@@ -427,12 +1402,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;
}
@@ -443,97 +1419,152 @@ 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(boolean isGLOriented) {
+ if( isInGLOrientation != isGLOriented ) {
+ if( DEBUG ) {
+ System.err.println("XXX gl-orient "+isInGLOrientation+" -> "+isGLOriented);
}
+ isInGLOrientation = isGLOriented;
+ 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;
+ public final URI getURI() {
+ return streamLoc;
}
- protected abstract void destroyImpl(GL gl);
@Override
- public final synchronized URLConnection getURLConnection() {
- return urlConn;
- }
+ public final int getVID() { return vid; }
+
+ @Override
+ public final int getAID() { return aid; }
@Override
- public final synchronized String getVideoCodec() {
+ public final String getVideoCodec() {
return vcodec;
}
@Override
- public final synchronized String getAudioCodec() {
+ public final String getAudioCodec() {
return acodec;
}
@Override
- public final synchronized long getTotalFrames() {
- return totalFrames;
+ public final int getVideoFrames() {
+ return videoFrames;
+ }
+
+ @Override
+ public final int getAudioFrames() {
+ return audioFrames;
}
@Override
- public final synchronized int getDuration() {
+ public final int getDuration() {
return duration;
}
-
+
@Override
- public final synchronized long getStreamBitrate() {
+ public final long getStreamBitrate() {
return bps_stream;
}
@Override
- public final synchronized int getVideoBitrate() {
+ public final int getVideoBitrate() {
return bps_video;
}
-
+
@Override
- public final synchronized int getAudioBitrate() {
+ public final int getAudioBitrate() {
return bps_audio;
}
-
+
@Override
- public final synchronized float getFramerate() {
+ public final float getFramerate() {
return fps;
}
@Override
- public final synchronized int getWidth() {
+ public final boolean isGLOriented() {
+ return isInGLOrientation;
+ }
+
+ @Override
+ public final int getWidth() {
return width;
}
@Override
- public final synchronized int getHeight() {
+ public final int getHeight() {
return height;
}
@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 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 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
@@ -557,13 +1588,30 @@ 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(String name) {
+ return attachedObjects.get(name);
+ }
+
+ @Override
+ public final Object attachObject(String name, Object obj) {
+ return attachedObjects.put(name, obj);
+ }
+
+ @Override
+ public final Object detachObject(String name) {
+ return attachedObjects.remove(name);
+ }
+
+ private final HashMap<String, Object> attachedObjects = new HashMap<String, Object>();
protected static final String toHexString(long v) {
return "0x"+Long.toHexString(v);
@@ -571,5 +1619,15 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
protected static final String toHexString(int v) {
return "0x"+Integer.toHexString(v);
}
-
-} \ No newline at end of file
+ protected static final int getPropIntVal(Map<String, String> props, String key) {
+ final String val = props.get(key);
+ try {
+ return Integer.valueOf(val).intValue();
+ } catch (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..6e006d9c0
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java
@@ -0,0 +1,229 @@
+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 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 (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(float rate) {
+ return false; // FIXME
+ }
+
+ @Override
+ public final float getVolume() {
+ // FIXME
+ return volume;
+ }
+
+ @Override
+ public final boolean setVolume(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(AudioSink.AudioFormat format) {
+ return true;
+ }
+
+ @Override
+ public boolean init(AudioSink.AudioFormat requestedFormat, float frameDuration, int initialQueueSize, int queueGrowAmount, 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 (Exception e) {
+ initialized=false;
+ }
+ return true;
+ }
+
+ @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(AudioDataFrame audioDataFrame) {
+ int byteSize = audioDataFrame.getByteSize();
+ final ByteBuffer byteBuffer = audioDataFrame.getData();
+ final byte[] bytes = new byte[byteSize];
+ final int p = byteBuffer.position();
+ byteBuffer.get(bytes, 0, byteSize);
+ byteBuffer.position(p);
+
+ int written = 0;
+ int len;
+ while (byteSize > 0) {
+ len = auline.write(bytes, written, byteSize);
+ byteSize -= len;
+ written += len;
+ }
+ playImpl();
+ return audioDataFrame;
+ }
+
+ @Override
+ public AudioFrame enqueueData(int pts, ByteBuffer bytes, int byteCount) {
+ return enqueueData(new AudioDataFrame(pts, chosenFormat.getBytesDuration(byteCount), bytes, 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(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..8d3dbdf44
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java
@@ -0,0 +1,129 @@
+package jogamp.opengl.util.av;
+
+
+import java.nio.ByteBuffer;
+
+import com.jogamp.opengl.util.av.AudioSink;
+
+public class NullAudioSink implements AudioSink {
+
+ @Override
+ public boolean isInitialized() {
+ return true;
+ }
+
+ private volatile float playSpeed = 1.0f;
+ private volatile boolean playRequested = false;
+ private float volume = 1.0f;
+
+ @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(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(AudioFormat format) {
+ return true;
+ }
+
+ @Override
+ public boolean init(AudioFormat requestedFormat, float frameDuration, int initialQueueSize, int queueGrowAmount, int queueLimit) {
+ return true;
+ }
+
+ @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() {
+ }
+
+ @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 0; }
+
+ @Override
+ public int getFreeFrameCount() {
+ return 1;
+ }
+
+ @Override
+ public AudioFrame enqueueData(AudioDataFrame audioDataFrame) {
+ return null;
+ }
+
+ @Override
+ public AudioFrame enqueueData(int pts, ByteBuffer bytes, int byteCount) {
+ 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 3d740d6b2..79129e563 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(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(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(GL gl, 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(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", NullGLMediaPlayer.class.getClassLoader());
+ URLConnection urlConn = IOUtil.getResource("jogl/util/data/av/test-ntsc01-57x32.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) {
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;
+ 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(int vid, 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(GL gl) throws IOException, GLException {
+ isInGLOrientation = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Returns {@link GLMediaPlayer#TEXTURE_COUNT_MIN}.
+ * </p>
+ */
+ @Override
+ protected int validateTextureCount(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(GL gl, int texName) {
+ final Texture texture = super.createTexImageImpl(gl, texName, width, height);
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(GL gl, TextureSequence.TextureFrame frame) {
+ super.destroyTexFrame(gl, frame);
}
-
+
private void validatePos() {
boolean considerPausing = false;
if( 0 > pos_ms) {
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 ce9df21cf..a6a6fba97 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,208 +41,377 @@ 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 */
+ boolean[] _loaded= new boolean[6];
+ /** util, format, codec, avresample, swresample */
+ VersionNumber[] _versions = new VersionNumber[5];
try {
- _ready = initSymbols();
+ _ready = initSymbols(_loaded, _versions);
} catch (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 {
+ if( avCodecVersion.getMajor() == 53 && avFormatVersion.getMajor() == 53 && avUtilVersion.getMajor() == 51 ) {
+ // lavc53.lavf53.lavu51
+ natives = new FFMPEGv08Natives();
+ } else if( avCodecVersion.getMajor() == 54 && avFormatVersion.getMajor() == 54 && avUtilVersion.getMajor() == 52 ) {
+ // lavc54.lavf54.lavu52.lavr01
+ natives = new FFMPEGv09Natives();
+ } else if( avCodecVersion.getMajor() == 55 && avFormatVersion.getMajor() == 55 && avUtilVersion.getMajor() == 52 ) {
+ // lavc55.lavf55.lavu52.lavr01
+ 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(boolean[] loaded, 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() {
+ public final List<List<String>> getToolLibNames() {
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("avutil-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-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("avformat-55"); // dummy future proof
- avformat.add("avformat-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-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("avcodec-55"); // dummy future proof
- avcodec.add("avcodec-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-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;
}
@@ -257,14 +426,12 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo {
}
@Override
- public boolean useToolGetProcAdressFirst(String funcName) {
+ public final boolean useToolGetProcAdressFirst(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 7d10cff4d..344ba48ee 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,797 @@
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.os.Platform;
+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.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.GLMediaPlayerImpl;
+import jogamp.opengl.util.av.impl.FFMPEGNatives.PixelFormat;
+import jogamp.opengl.util.av.impl.FFMPEGNatives.SampleFormat;
/***
* 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 PixelFormat#YUV420P}</li>
+ * <li>{@link PixelFormat#YUVJ420P}</li>
+ * <li>{@link PixelFormat#YUV422P}</li>
+ * <li>{@link PixelFormat#YUVJ422P}</li>
+ * <li>{@link PixelFormat#YUYV422}</li>
+ * <li>{@link PixelFormat#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>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: http://ffmpegmac.net/</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"));
+ }
+ libAVVersionGood = avCodecMajorVersionCC == avCodecVersion.getMajor() &&
+ avFormatMajorVersionCC == avFormatVersion.getMajor() &&
+ avUtilMajorVersionCC == avUtilVersion.getMajor() &&
+ ( !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 PixelFormat 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(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(int vid, 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.decodeURIIfFilePath(streamLoc);
+ 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(Platform.OS_TYPE) {
+ case ANDROID:
+ // ??
+ case FREEBSD:
+ case HPUX:
+ case LINUX:
+ case SUNOS:
+ resStreamLocS = dev_video_linux + cameraPath;
+ break;
+ case WINDOWS:
+ resStreamLocS = cameraPath;
+ 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 "+streamLoc+" -> "+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(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;
- switch(vBytesPerPixelPerPlane) {
- case 1: tf = GL2ES2.GL_RED; break;
- case 3: tf = GL2ES2.GL_RGB; break;
- case 4: tf = GL2ES2.GL_RGBA; break;
- default: throw new RuntimeException("Unsupported bytes-per-pixel / plane "+vBytesPerPixelPerPlane);
- }
- setTextureFormat(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 != vid ) {
+ 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 == aid ) {
+ 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 != vid ) {
+ int tf, tif=GL.GL_RGBA; // texture format and internal format
+ 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 = GL2ES2.GL_ALPHA; tif=GL2ES2.GL_ALPHA; singleTexComp = "a";
+ }
+ break;
+
+ case 2: if( vPixelFmt == PixelFormat.YUYV422 ) {
+ // YUYV422: // < packed YUV 4:2:2, 2x 16bpp, Y0 Cb Y1 Cr
+ // Stuffed into RGBA half width texture
+ tf = GL2ES2.GL_RGBA; tif=GL2ES2.GL_RGBA; break;
+ } else {
+ tf = GL2ES2.GL_RG; tif=GL2ES2.GL_RG; break;
+ }
+ case 3: tf = GL2ES2.GL_RGB; tif=GL.GL_RGB; break;
+ case 4: if( vPixelFmt == PixelFormat.BGRA ) {
+ tf = GL2ES2.GL_BGRA; tif=GL.GL_RGBA; break;
+ } else {
+ tf = GL2ES2.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(GL gl, 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 SampleFormat}.
+ * @param audioSampleRate sample rate in Hz (1/s)
+ * @param audioChannels number of channels
+ */
+ final boolean isAudioFormatSupported(int audioSampleFmt, int audioSampleRate, int audioChannels) {
+ final SampleFormat avFmt = SampleFormat.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 SampleFormat}.
+ * @param audioSampleRate sample rate in Hz (1/s)
+ * @param audioChannels number of channels
+ */
+ private final AudioFormat avAudioFormat2Local(SampleFormat audioSampleFmt, int audioSampleRate, 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(int vid, int pixFmt, int planes, int bitsPerPixel, int bytesPerPixelPerPlane,
+ int tWd0, int tWd1, int tWd2, int vW, int vH,
+ int aid, int audioSampleFmt, int audioSampleRate,
+ int audioChannels, 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 = PixelFormat.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 SampleFormat aSampleFmt;
+ avChosenAudioFormat = null;;
+ this.audioSamplesPerFrameAndChannel = 0;
+
+ if( STREAM_ID_NONE != aid ) {
+ aSampleFmt = SampleFormat.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(boolean isInGLOrientation, int pixFmt, int planes, int bitsPerPixel, int bytesPerPixelPerPlane,
+ int tWd0, int tWd1, int tWd2, int vW, 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 {
+ public final String getTextureLookupFunctionName(String desiredFuncName) throws IllegalStateException {
if(State.Uninitialized == state) {
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 {
+ public final String getTextureLookupFragmentShaderImpl() throws IllegalStateException {
if(State.Uninitialized == state) {
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"+
+ 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"+
" vec2 u_off = vec2("+tc_w_1+", 0.0);\n"+
" 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).r;\n"+
- " u = texture2D(image, u_off+tc_half).r;\n"+
- " v = texture2D(image, v_off+tc_half).r;\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"+
+ " vec2 u_off = vec2("+tc_w_1+" , 0.0);\n"+
+ " 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(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(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(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(GL gl, 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, textureTarget, textureFormat, textureType);
+ }
+ 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(ByteBuffer sampleData, int data_size, int audio_pts) {
+ setFirstAudioPTS2SCR( audio_pts );
+ if( 1.0f == playSpeed || 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..b4b887bc9
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGNatives.java
@@ -0,0 +1,281 @@
+/**
+ * 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(long[] symbols, 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);
+
+ /** FFMPEG/libAV Audio Sample Format */
+ public static enum SampleFormat {
+ // 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 SampleFormat valueOf(int ordinal) throws IllegalArgumentException {
+ final SampleFormat[] all = SampleFormat.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)+"]");
+ }
+ };
+
+ /** FFMPEG/libAV Pixel Format */
+ public static enum PixelFormat {
+ // 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 PixelFormat valueOf(int ordinal) throws IllegalArgumentException {
+ final PixelFormat[] all = PixelFormat.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/FFMPEGStaticNatives.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGStaticNatives.java
new file mode 100644
index 000000000..22a045825
--- /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(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..05a94def8 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,51 +70,60 @@ 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(GL gl, 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(GL gl, TextureSequence.TextureFrame imgTex) {
+ super.destroyTexFrame(gl, imgTex);
}
-
+
@Override
protected void destroyImpl(GL gl) {
- _detachVideoRenderer(moviePtr);
if (moviePtr != 0) {
+ _stop(moviePtr);
+ _detachVideoRenderer(moviePtr);
_destroyInstance(moviePtr);
moviePtr = 0;
}
}
-
+
@Override
- protected void initGLStreamImpl(GL gl, int[] texNames) throws IOException {
+ protected void initStreamImpl(int vid, 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(!streamLoc.getScheme().equals("file")) {
+ throw new IOException("Only file schemes are allowed: "+streamLoc);
+ }
+ final String path=streamLoc.getPath();
+ if(DEBUG) {
+ System.out.println("initGLStream: clean path "+path);
+ }
+
+ if(DEBUG) {
+ System.out.println("initGLStream: p1 "+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);
+ if(DEBUG) {
+ System.out.println("initGLStream: p2 "+this);
+ }
}
-
@Override
- protected int getCurrentPositionImpl() {
+ protected final void initGLImpl(GL gl) throws IOException, GLException {
+ // NOP
+ isInGLOrientation = true;
+ }
+
+ @Override
+ protected int getAudioPTSImpl() {
return 0!=moviePtr ? _getCurrentPosition(moviePtr) : 0;
}
@@ -130,7 +137,7 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
}
@Override
- public synchronized boolean startImpl() {
+ public synchronized boolean playImpl() {
if(0==moviePtr) {
return false;
}
@@ -150,16 +157,6 @@ 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) {
if(0==moviePtr) {
throw new GLException("OMX native instance null");
@@ -168,36 +165,33 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
}
@Override
- protected TextureSequence.TextureFrame getLastTextureImpl() {
- return lastTex;
- }
-
- @Override
- protected TextureSequence.TextureFrame getNextTextureImpl(GL gl, boolean blocking) {
+ protected int getNextTextureImpl(GL gl, 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;
+ 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) {
@@ -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..1f402f48b 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandler.java
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandler.java
@@ -33,40 +33,47 @@ 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;
+ super(ad);
}
-
+
+ @Override
public final void setSubArrayVBOName(int vboName) {
throw new UnsupportedOperationException();
}
-
+
+ @Override
public final void addSubHandler(GLArrayHandlerFlat handler) {
throw new UnsupportedOperationException();
}
-
- public final void syncData(GL gl, boolean enable, Object ext) {
+
+ @Override
+ public final void enableState(GL gl, boolean enable, 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 void enableShaderState(GL2ES2 glsl, boolean enable, 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 +85,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,6 +95,7 @@ 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)
@@ -95,26 +104,69 @@ public class GLSLArrayHandler implements GLArrayHandler {
if(ad.getVBOName() != qi[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(GL2ES2 glsl, 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)
+ final int[] qi = new int[1];
+ glsl.glGetVertexAttribiv(location, GL2ES2.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, qi, 0);
+ if(ad.getVBOName() != qi[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..34a381d7d 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerFlat.java
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLArrayHandlerFlat.java
@@ -37,7 +37,7 @@ 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 {
@@ -47,43 +47,57 @@ public class GLSLArrayHandlerFlat implements GLArrayHandlerFlat {
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(GL gl, 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);
+ }
+ }*/
}
+ @Override
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);
+ 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..e153082e0 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;
+ super(ad);
}
-
+
+ @Override
public final void setSubArrayVBOName(int vboName) {
for(int i=0; i<subArrays.size(); i++) {
subArrays.get(i).getData().setVBOName(vboName);
- }
+ }
}
-
+
+ @Override
public final void addSubHandler(GLArrayHandlerFlat handler) {
subArrays.add(handler);
}
- private final void syncSubData(GL gl, boolean enable, boolean force, Object ext) {
+ private final void syncSubData(GL gl, 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(GL gl, boolean enable, 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..dba408554
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/GLSLTextureRaster.java
@@ -0,0 +1,195 @@
+/**
+ * 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(int textureUnit, 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(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(PMVMatrix.GL_PROJECTION);
+ pmvMatrix.glLoadIdentity();
+ pmvMatrix.glMatrixMode(PMVMatrix.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(GL2ES2 gl, int x, int y, int width, 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(GL2ES2 gl) {
+ if(null != pmvMatrixUniform) {
+ pmvMatrixUniform = null;
+ }
+ if(null != pmvMatrix) {
+ pmvMatrix.destroy();
+ pmvMatrix=null;
+ }
+ if(null != interleavedVBO) {
+ interleavedVBO.destroy(gl);
+ interleavedVBO=null;
+ }
+ if(null != sp) {
+ sp.destroy(gl);
+ sp=null;
+ }
+ }
+
+ public void display(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..458a9c94f 100644
--- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java
+++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java
@@ -29,87 +29,115 @@
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.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 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 (GL2ES2 gl, ShaderSelectionMode mode, PMVMatrix pmvMatrix) {
+ 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(GL2ES2 gl, ShaderSelectionMode mode, PMVMatrix pmvMatrix,
+ Class<?> shaderRootClass, String shaderSrcRoot, String shaderBinRoot,
+ String vertexColorFile, String vertexColorLightFile,
+ String fragmentColorFile, String fragmentColorTextureFile) {
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(boolean v) { fixedFunction.setVerbose(v); }
public void destroy() {
fixedFunction.destroy(gl);
fixedFunction = null;
+ if(ownsPMVMatrix) {
+ pmvMatrix.destroy();
+ }
+ 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);
+ fixedFunction.glDrawArrays(gl, mode, first, count);
}
public void glDrawElements(int mode, int count, int type, java.nio.Buffer indices) {
- fixedFunction.validate(gl);
- gl.glDrawElements(mode, count, type, indices);
+ fixedFunction.glDrawElements(gl, mode, count, type, indices);
}
public void glDrawElements(int mode, int count, int type, long indices_buffer_offset) {
- fixedFunction.validate(gl);
- gl.glDrawElements(mode, count, type, indices_buffer_offset);
+ fixedFunction.glDrawElements(gl, mode, count, type, indices_buffer_offset);
}
public void glActiveTexture(int texture) {
- fixedFunction.glActiveTexture(gl, texture);
+ fixedFunction.glActiveTexture(texture);
gl.glActiveTexture(texture);
}
public void glEnable(int cap) {
- if(fixedFunction.glEnable(gl, cap, true)) {
+ if(fixedFunction.glEnable(cap, true)) {
gl.glEnable(cap);
}
}
public void glDisable(int cap) {
- if(fixedFunction.glEnable(gl, cap, false)) {
+ if(fixedFunction.glEnable(cap, false)) {
gl.glDisable(cap);
}
}
- public void glCullFace(int faceName) {
- fixedFunction.glCullFace(gl, faceName);
- gl.glCullFace(faceName);
- }
-
+ @Override
public void glGetFloatv(int pname, java.nio.FloatBuffer params) {
if(PMVMatrix.isMatrixGetName(pname)) {
pmvMatrix.glGetFloatv(pname, params);
@@ -117,6 +145,7 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
}
gl.glGetFloatv(pname, params);
}
+ @Override
public void glGetFloatv(int pname, float[] params, int params_offset) {
if(PMVMatrix.isMatrixGetName(pname)) {
pmvMatrix.glGetFloatv(pname, params, params_offset);
@@ -124,6 +153,7 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
}
gl.glGetFloatv(pname, params, params_offset);
}
+ @Override
public void glGetIntegerv(int pname, IntBuffer params) {
if(PMVMatrix.isMatrixGetName(pname)) {
pmvMatrix.glGetIntegerv(pname, params);
@@ -131,6 +161,7 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
}
gl.glGetIntegerv(pname, params);
}
+ @Override
public void glGetIntegerv(int pname, int[] params, int params_offset) {
if(PMVMatrix.isMatrixGetName(pname)) {
pmvMatrix.glGetIntegerv(pname, params, params_offset);
@@ -139,95 +170,193 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
gl.glGetIntegerv(pname, params, params_offset);
}
- //
+ public void glTexEnvi(int target, int pname, int value) {
+ fixedFunction.glTexEnvi(target, pname, value);
+ }
+ public void glGetTexEnviv(int target, int pname, IntBuffer params) {
+ fixedFunction.glGetTexEnviv(target, pname, params);
+ }
+ public void glGetTexEnviv(int target, int pname, int[] params, int params_offset) {
+ fixedFunction.glGetTexEnviv(target, pname, params, params_offset);
+ }
+ public void glBindTexture(int target, int texture) {
+ fixedFunction.glBindTexture(target, texture);
+ gl.glBindTexture(target, texture);
+ }
+ public void glTexImage2D(int target, int level, int internalformat, int width, int height, int border,
+ int format, int type, 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(int target, int level, int internalformat, int width, int height, int border,
+ int format, int type, 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(float size) {
+ fixedFunction.glPointSize(size);
+ }
+ public void glPointParameterf(int pname, float param) {
+ fixedFunction.glPointParameterf(pname, param);
+ }
+ public void glPointParameterfv(int pname, float[] params, int params_offset) {
+ fixedFunction.glPointParameterfv(pname, params, params_offset);
+ }
+ public void glPointParameterfv(int pname, java.nio.FloatBuffer params) {
+ fixedFunction.glPointParameterfv(pname, params);
+ }
+
+ //
// MatrixIf
//
public int glGetMatrixMode() {
return pmvMatrix.glGetMatrixMode();
}
+ @Override
public void glMatrixMode(int mode) {
pmvMatrix.glMatrixMode(mode);
}
+ @Override
public void glLoadMatrixf(java.nio.FloatBuffer m) {
pmvMatrix.glLoadMatrixf(m);
}
+ @Override
public void glLoadMatrixf(float[] m, int m_offset) {
glLoadMatrixf(GLBuffers.newDirectFloatBuffer(m, m_offset));
}
+ @Override
public void glPopMatrix() {
pmvMatrix.glPopMatrix();
}
+ @Override
public void glPushMatrix() {
pmvMatrix.glPushMatrix();
}
+ @Override
public void glLoadIdentity() {
pmvMatrix.glLoadIdentity();
}
+ @Override
public void glMultMatrixf(java.nio.FloatBuffer m) {
pmvMatrix.glMultMatrixf(m);
}
+ @Override
public void glMultMatrixf(float[] m, int m_offset) {
glMultMatrixf(GLBuffers.newDirectFloatBuffer(m, m_offset));
}
+ @Override
public void glTranslatef(float x, float y, float z) {
pmvMatrix.glTranslatef(x, y, z);
}
+ @Override
public void glRotatef(float angdeg, float x, float y, float z) {
pmvMatrix.glRotatef(angdeg, x, y, z);
}
+ @Override
public void glScalef(float x, float y, float z) {
pmvMatrix.glScalef(x, y, z);
}
+ public void glOrtho(double left, double right, double bottom, double top, double near_val, double far_val) {
+ glOrthof((float) left, (float) right, (float) bottom, (float) top, (float) near_val, (float) far_val);
+ }
+ @Override
public void glOrthof(float left, float right, float bottom, float top, float zNear, float zFar) {
pmvMatrix.glOrthof(left, right, bottom, top, zNear, zFar);
}
+ public void glFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
+ glFrustumf((float) left, (float) right, (float) bottom, (float) top, (float) zNear, (float) zFar);
+ }
+ @Override
public void glFrustumf(float left, float right, float bottom, float top, float zNear, float zFar) {
pmvMatrix.glFrustumf(left, right, bottom, top, zNear, zFar);
}
- //
+ //
// LightingIf
//
+ @Override
public void glColor4f(float red, float green, float blue, float alpha) {
- fixedFunction.glColor4fv(gl, GLBuffers.newDirectFloatBuffer(new float[] { red, green, blue, alpha }));
+ fixedFunction.glColor4f(gl, red, green, blue, alpha);
}
+ public void glColor4ub(byte red, byte green, byte blue, 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(int light, int pname, java.nio.FloatBuffer params) {
fixedFunction.glLightfv(gl, light, pname, params);
}
+ @Override
public void glLightfv(int light, int pname, float[] params, int params_offset) {
glLightfv(light, pname, GLBuffers.newDirectFloatBuffer(params, params_offset));
}
+ @Override
public void glMaterialfv(int face, int pname, java.nio.FloatBuffer params) {
fixedFunction.glMaterialfv(gl, face, pname, params);
}
+ @Override
public void glMaterialfv(int face, int pname, float[] params, int params_offset) {
glMaterialfv(face, pname, GLBuffers.newDirectFloatBuffer(params, params_offset));
}
+ @Override
public void glMaterialf(int face, int pname, float param) {
glMaterialfv(face, pname, GLBuffers.newDirectFloatBuffer(new float[] { param }));
}
+
+ //
+ // Misc Simple States
+ //
+ @Override
public void glShadeModel(int mode) {
fixedFunction.glShadeModel(gl, mode);
}
+ public void glAlphaFunc(int func, float ref) {
+ fixedFunction.glAlphaFunc(func, ref);
+ }
+
+ /** ES2 supports CullFace implicit
+ public void glCullFace(int faceName) {
+ fixedFunction.glCullFace(faceName);
+ gl.glCullFace(faceName);
+ } */
//
// PointerIf
//
+ public void glClientActiveTexture(int textureUnit) {
+ fixedFunction.glClientActiveTexture(textureUnit);
+ }
+ @Override
public void glEnableClientState(int glArrayIndex) {
fixedFunction.glEnableClientState(gl, glArrayIndex);
}
+ @Override
public void glDisableClientState(int glArrayIndex) {
fixedFunction.glDisableClientState(gl, glArrayIndex);
}
+ @Override
public void glVertexPointer(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 +366,29 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
fixedFunction.glVertexPointer(gl, array);
}
+ @Override
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));
+ glVertexPointer(GLArrayDataWrapper.createFixed(GL_VERTEX_ARRAY, size, type, GLBuffers.isGLTypeFixedPoint(type), stride,
+ pointer, 0, 0, 0, GL.GL_ARRAY_BUFFER));
}
+ @Override
public void glVertexPointer(int size, int type, int stride, long pointer_buffer_offset) {
- int vboName = gl.glGetBoundBuffer(GL.GL_ARRAY_BUFFER);
+ 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));
}
+ @Override
public void glColorPointer(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 +397,32 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
}
fixedFunction.glColorPointer(gl, array);
}
+ @Override
public void glColorPointer(int size, int type, int stride, java.nio.Buffer pointer) {
- glColorPointer(GLArrayDataWrapper.createFixed(GL_COLOR_ARRAY, size, type, false, stride,
+ glColorPointer(GLArrayDataWrapper.createFixed(GL_COLOR_ARRAY, size, type, GLBuffers.isGLTypeFixedPoint(type), stride,
pointer, 0, 0, 0, GL.GL_ARRAY_BUFFER));
}
+ @Override
public void glColorPointer(int size, int type, int stride, long pointer_buffer_offset) {
- int vboName = gl.glGetBoundBuffer(GL.GL_ARRAY_BUFFER);
+ 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));
}
+ @Override
public void glNormalPointer(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 +431,29 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
}
fixedFunction.glNormalPointer(gl, array);
}
+ @Override
public void glNormalPointer(int type, int stride, java.nio.Buffer pointer) {
- glNormalPointer(GLArrayDataWrapper.createFixed(GL_NORMAL_ARRAY, 3, type, false, stride,
+ glNormalPointer(GLArrayDataWrapper.createFixed(GL_NORMAL_ARRAY, 3, type, GLBuffers.isGLTypeFixedPoint(type), stride,
pointer, 0, 0, 0, GL.GL_ARRAY_BUFFER));
}
+ @Override
public void glNormalPointer(int type, int stride, long pointer_buffer_offset) {
- int vboName = gl.glGetBoundBuffer(GL.GL_ARRAY_BUFFER);
+ 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));
}
+ @Override
public void glTexCoordPointer(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 +462,29 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun
}
fixedFunction.glTexCoordPointer(gl, array);
}
+ @Override
public void glTexCoordPointer(int size, int type, int stride, 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));
}
+ @Override
public void glTexCoordPointer(int size, int type, int stride, long pointer_buffer_offset) {
- int vboName = gl.glGetBoundBuffer(GL.GL_ARRAY_BUFFER);
+ 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();
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..42269588d 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,87 @@
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.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 = Debug.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(GL2ES2 gl, ShaderSelectionMode mode, 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(GL2ES2 gl, ShaderSelectionMode mode, PMVMatrix pmvMatrix,
+ Class<?> shaderRootClass, String shaderSrcRoot,
+ String shaderBinRoot,
+ String vertexColorFile, String vertexColorLightFile,
+ String fragmentColorFile, 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(ShaderSelectionMode mode) { requestedShaderSelectionMode=mode; }
+ public ShaderSelectionMode getCurrentShaderSelectionMode() { return currentShaderSelectionMode; }
+
public boolean verbose() { return verbose; }
- public void setVerbose(boolean v) { verbose=v; }
+ public void setVerbose(boolean v) { verbose = DEBUG || v; }
public boolean isValid() {
return shaderState.linked();
@@ -69,46 +123,83 @@ 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);
+ 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(GL2ES2 gl, float red, float green, float blue, 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");
}
}
+ //
+ // Arrays / States
+ //
+
+ public void glEnableClientState(GL2ES2 gl, int glArrayIndex) {
+ glToggleClientState(gl, glArrayIndex, true);
+ }
+
public void glDisableClientState(GL2ES2 gl, int glArrayIndex) {
- shaderState.useProgram(gl, true);
+ 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(GL2ES2 gl, int glArrayIndex, 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;
}
}
@@ -122,26 +213,235 @@ public class FixedFuncPipeline {
shaderState.vertexAttribPointer(gl, data);
}
- public void glColor4fv(GL2ES2 gl, FloatBuffer data ) {
+ public void glNormalPointer(GL2ES2 gl, 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(boolean enable, 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 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(GL2ES2 gl, 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 glBindTexture(int target, 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(int target, /* int level, */ int internalformat, /*, int width, int height, int border, */
+ 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(int target, int pname, 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 GL2ES1.GL_BLEND:
+ mode = 4;
+ break;
+ case GL2ES1.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(int value) {
+ if( value != textureEnvMode.get(activeTextureUnit) ) {
+ textureEnvMode.put(activeTextureUnit, value);
+ textureEnvModeDirty = true;
+ }
+ }
+ public void glGetTexEnviv(int target, int pname, IntBuffer params) { // FIXME
+ System.err.println("FixedFuncPipeline: Unimplemented glGetTexEnviv: target "+toHexString(target)+", pname "+toHexString(pname));
+ }
+ public void glGetTexEnviv(int target, int pname, int[] params, int params_offset) { // FIXME
+ System.err.println("FixedFuncPipeline: Unimplemented glGetTexEnviv: target "+toHexString(target)+", pname "+toHexString(pname));
+ }
+
+ //
+ // Point Sprites
+ //
+ public void glPointSize(float size) {
+ pointParams.put(0, size);
+ pointParamsDirty = true;
+ }
+ public void glPointParameterf(int pname, 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 GL2ES2.GL_POINT_FADE_THRESHOLD_SIZE:
+ pointParams.put(4+3, param);
+ break;
+ }
+ pointParamsDirty = true;
+ }
+ public void glPointParameterfv(int pname, float[] params, 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(int pname, 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(GL2ES2 gl, GLRunnable2<Object,Object> glDrawAction, 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(GL gl, Object args) {
+ int[] _args = (int[])args;
+ gl.glDrawArrays(GL.GL_POINTS, _args[0], _args[1]);
+ return null;
+ }
+ };
+ private final void glDrawPointArrays(GL2ES2 gl, int first, int count) {
+ glDrawPoints(gl, glDrawArraysAction, new int[] { first, count });
+ }
+
+ //
+ // Lighting
+ //
+
public void glLightfv(GL2ES2 gl, int light, int pname, java.nio.FloatBuffer params) {
shaderState.useProgram(gl, true);
light -=GLLightingFunc.GL_LIGHT0;
@@ -179,17 +479,14 @@ 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);
}
}
@@ -201,10 +498,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 +509,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,17 +530,20 @@ 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) {
+
}
}
+ //
+ // Misc States
+ //
+
public void glShadeModel(GL2ES2 gl, int mode) {
shaderState.useProgram(gl, true);
GLUniformData ud = shaderState.getUniform(mgl_ShadeModel);
@@ -249,46 +553,135 @@ public class FixedFuncPipeline {
}
}
- 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(int func, 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(int cap, 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;
@@ -299,157 +692,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(GL2ES2 gl, int mode, int first, 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 GL2ES1.GL_POINTS:
+ glDrawPointArrays(gl, first, count);
+ return;
}
- if(0>cullFace) {
- faceName *= -1;
+ validate(gl, true);
+ if ( GL2.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(GL2ES2 gl, int mode, int count, int type, java.nio.Buffer indices) {
+ validate(gl, true);
+ if ( GL2.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, (int)(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, (int)(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, (int)(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( GL2ES1.GL_POINTS != mode ) {
+ ((GLES2)gl).glDrawElements(mode, count, type, indices);
+ } else {
+ // FIXME GL_POINTS !
+ ((GLES2)gl).glDrawElements(mode, count, type, indices);
+ }
+ }
+ }
+ public void glDrawElements(GL2ES2 gl, int mode, int count, int type, long indices_buffer_offset) {
+ validate(gl, true);
+ if ( GL2.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( GL2ES1.GL_POINTS != mode ) {
+ // FIXME GL_POINTS !
+ gl.glDrawElements(mode, count, type, indices_buffer_offset);
+ } else {
+ 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(GL2ES2 gl, 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) {
+ 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, 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(GL2ES2 gl, ShaderCode vp, ShaderCode fp, String maxTextureDefine) {
+ int rsVpPos = vp.defaultShaderCustomization(gl, true, true);
+ 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(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);
+ }
+ }
- shaderProgramColor = new ShaderProgram();
- shaderProgramColor.add(vertexColor);
- shaderProgramColor.add(fragmentColor);
- if(!shaderProgramColor.link(gl, System.err)) {
- throw new GLException("Couldn't link VertexColor program: "+shaderProgramColor);
+ private final void loadShader(GL2ES2 gl, 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;
+
+ 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(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(GL2ES2 gl, ShaderSelectionMode mode, 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 +1115,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 +1143,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 +1152,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(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..45087ea2c
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/jpeg/JPEGDecoder.java
@@ -0,0 +1,1517 @@
+/**
+ * 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) | data[9];
+ yDensity = (data[10] << 8) | data[11];
+ 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) | data[8] ) ;
+ flags1 = (short) ( (data[9] << 8) | data[10] ) ;
+ 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(String message) {
+ super(message);
+ }
+ }
+ @SuppressWarnings("serial")
+ public static class MarkerException extends CodecException {
+ final int marker;
+ MarkerException(int marker, 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(boolean progressive, int precision, int scanLines, int samplesPerLine, int componentsCount, 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(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(int compID, ComponentIn component) {
+ if( maxCompID < compID ) {
+ maxCompID = compID;
+ }
+ final int idx = compIDs.size();
+ checkBounds(idx);
+ compIDs.add(compID);
+ comps[idx] = component;
+ }
+ public final ComponentIn getCompByIndex(int i) {
+ checkBounds(i);
+ return comps[i];
+ }
+ public final ComponentIn getCompByID(int componentID) {
+ return getCompByIndex( compIDs.indexOf(componentID) );
+ }
+ public final int getCompID(int idx) {
+ return compIDs.get(idx);
+ }
+ public final boolean hasCompID(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 */
+ 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(int h, int v, int qttIdx) {
+ this.h = h;
+ this.v = v;
+ this.qttIdx = qttIdx;
+ }
+
+ public final void allocateBlocks(int blocksPerColumn, int blocksPerColumnForMcu, int blocksPerLine, 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(int row, 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 */
+ class ComponentOut {
+ private final ArrayList<byte[]> lines;
+ final float scaleX;
+ final float scaleY;
+
+ ComponentOut(ArrayList<byte[]> lines, float scaleX, 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(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(InputStream is) {
+ try {
+ bstream.setStream(is, false /* outputMode */);
+ } catch (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;
+ 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(byte[] data, int offset, 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(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++;
+ 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(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;
+ }
+ int mcusPerLine = (int) Math.ceil(frame.samplesPerLine / 8f / maxH);
+ 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;
+ }
+
+ private static class BinObjIdxed {
+ final BinObj children;
+ byte index;
+ BinObjIdxed() {
+ this.children = new BinObj();
+ this.index = 0;
+ }
+ }
+ private static class BinObj {
+ final boolean isValue;
+ final BinObj[] tree;
+ final byte b;
+
+ BinObj(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(int i) { return tree[i]; }
+ final void set(byte i, byte v) { tree[i] = new BinObj(v); }
+ final void set(byte i, BinObj v) { tree[i] = v; }
+ }
+
+ private BinObj buildHuffmanTable(int[] codeLengths, 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();
+ private static class Output {
+ private int blocksPerLine;
+ private int blocksPerColumn;
+ private int samplesPerLine;
+
+ private ArrayList<byte[]> buildComponentData(Frame frame, ComponentIn component) {
+ 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(int[] zz, byte[] dataOut, int[] dataIn, int[] qt) {
+ int v0, v1, v2, v3, v4, v5, v6, v7, t;
+ 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) {
+ 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) {
+ 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) {
+ int sample = 128 + ((p[i] + 8) >> 4);
+ dataOut[i] = (byte) ( sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample );
+ }
+ }
+ }
+
+ private static interface DecoderFunction {
+ void decode(ComponentIn component, int[] zz) throws IOException;
+ }
+
+ private 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(Frame frame, ArrayList<ComponentIn> components, int resetInterval,
+ int spectralStart, int spectralEnd, int successivePrev, 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 (MarkerException markerException) {
+ if(DEBUG) { System.err.println("JPEG.decodeScan: Marker exception: "+markerException.getMessage()); markerException.printStackTrace(); }
+ return markerException.getMarker();
+ } catch (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(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(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(ComponentIn component, 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(ComponentIn component, 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(ComponentIn component, int[] zz) throws IOException {
+ zz[0] |= readBit() << successive;
+ }
+ }
+
+ class ACFirstDecoder implements DecoderFunction {
+ @Override
+ public void decode(ComponentIn component, int[] zz) throws IOException {
+ if (eobrun > 0) {
+ eobrun--;
+ return;
+ }
+ int k = spectralStart, 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(ComponentIn component, int[] zz) throws IOException {
+ int k = spectralStart, e = spectralEnd, 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(ComponentIn component, DecoderFunction decoder, int mcu, int row, 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(ComponentIn component, DecoderFunction decoder, 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(JPEGDecoder.ColorSink pixelStorage, int width, 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(float a) {
+ return (byte) ( a < 0f ? 0 : a > 255f ? 255 : a );
+ }
+
+ private static String toHexString(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..5e177b8c3 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) {
this.val = val;
}
- public static FilterType getByVal(int i) {
+ private static HashMap<Integer, FilterType> byVal;
+
+ static {
+ byVal = new HashMap<Integer, FilterType>();
for (FilterType ft : values()) {
- if (ft.val == i)
- return ft;
+ byVal.put(ft.val, 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);
- }
-
- 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;
- }
-
- 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(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..79eed8f85 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;
@@ -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..ac7b858e1 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,10 +78,16 @@ 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)
@@ -89,13 +98,14 @@ public class ImageInfo {
/**
* 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
@@ -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,16 +167,11 @@ 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;
}
@@ -183,12 +188,6 @@ public class ImageInfo {
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..e6afd8694 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(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(ImageInfo imgInfo, SampleType stype, boolean unpackedMode) {
+ this(imgInfo, stype, unpackedMode, null, null);
+ }
+
+ /**
+ * If a preallocated array is passed, the copy is shallow
+ */
+ ImageLine(ImageInfo imgInfo, SampleType stype, boolean unpackedMode, int[] sci, 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 */
+ /** Sets row number (0 : Rows-1) */
public void setRown(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() {
+ 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() {
+ 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(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(ImageLine line) {
+ System.out.println(line);
+ 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..4636c3955
--- /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, PngChunkPLTE pal, PngChunkTRNS trns, int[] buf) {
+ boolean isalpha = trns != null;
+ int channels = isalpha ? 4 : 3;
+ int nsamples = line.imgInfo.cols * channels;
+ if (buf == null || buf.length < nsamples)
+ buf = new int[nsamples];
+ if (!line.samplesUnpacked)
+ line = line.unpackToNewImageLine();
+ boolean isbyte = line.sampleType == SampleType.BYTE;
+ int nindexesWithAlpha = trns != null ? trns.getPalletteAlpha().length : 0;
+ for (int c = 0; c < line.imgInfo.cols; c++) {
+ int index = isbyte ? (line.scanlineb[c] & 0xFF) : line.scanline[c];
+ pal.getEntryRgb(index, buf, c * channels);
+ if (isalpha) {
+ int alpha = index < nindexesWithAlpha ? trns.getPalletteAlpha()[index] : 255;
+ buf[c * channels + 3] = alpha;
+ }
+ }
+ return buf;
+ }
+
+ public static int[] palette2rgb(ImageLine line, PngChunkPLTE pal, 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(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(ImageLine line) {
+ 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(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(ImageLine line, int column) {
+ int offset = column * line.channels;
+ return (line.scanline[offset] << 16) + (line.scanline[offset + 1] << 8) + (line.scanline[offset + 2]);
+ }
+
+ public static int getPixelARGB8(ImageLine line, int column) {
+ 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(ImageLine line, 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(ImageLine line, int col, int r, int g, int b) {
+ col *= line.channels;
+ line.scanline[col++] = r;
+ line.scanline[col++] = g;
+ line.scanline[col] = b;
+ }
+
+ public static void setPixelRGB8(ImageLine line, int col, int rgb) {
+ setPixelRGB8(line, col, (rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
+ }
+
+ public static void setPixelsRGBA8(ImageLine line, 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(ImageLine line, int col, int r, int g, int b, 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(ImageLine line, int col, int rgb) {
+ setPixelRGBA8(line, col, (rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, (rgb >> 24) & 0xFF);
+ }
+
+ public static void setValD(ImageLine line, int i, double d) {
+ line.scanline[i] = double2int(line, d);
+ }
+
+ public static int interpol(int a, int b, int c, int d, double dx, double dy) {
+ // a b -> x (0-1)
+ // c d
+ //
+ double e = a * (1.0 - dx) + b * dx;
+ double f = c * (1.0 - dx) + d * dx;
+ return (int) (e * (1 - dy) + f * dy + 0.5);
+ }
+
+ public static double int2double(ImageLine line, int p) {
+ return line.bitDepth == 16 ? p / 65535.0 : p / 255.0;
+ // TODO: replace my multiplication? check for other bitdepths
+ }
+
+ public static double int2doubleClamped(ImageLine line, int p) {
+ // TODO: replace my multiplication?
+ 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(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(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(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);
+ }
+
+ /**
+ * 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(ImageInfo imgInfo, int[] src, int[] dst, boolean scale) {
+ int len1 = imgInfo.samplesPerRow;
+ 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(ImageInfo imgInfo, byte[] src, byte[] dst, boolean scale) {
+ int len1 = imgInfo.samplesPerRow;
+ 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(ImageInfo imgInfo, int[] src, int[] dst, boolean scale) {
+ 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(ImageInfo imgInfo, byte[] src, byte[] dst, boolean scale) {
+ 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(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(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..fb2cf5910
--- /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(ImageInfo imgInfo, SampleType stype, boolean unpackedMode, int rowOffset, int nRows, 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(int imrow) {
+ 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;
+ 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(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(int mrow) {
+ if (mrow < 0 || mrow > nRows)
+ throw new PngjException("Bad row " + mrow + ". Should be positive and less than " + nRows);
+ 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..e099c4f6a
--- /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(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(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(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(int[] src, int[] dst, 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(int[] src, 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(byte[] src, byte[] dst, 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(byte[] src, 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(int[][] imageInt) {
+ this.imageInt = imageInt;
+ }
+
+ short[][] getImageShort() {
+ return imageShort;
+ }
+
+ void setImageShort(short[][] imageShort) {
+ this.imageShort = imageShort;
+ }
+
+ byte[][] getImageByte() {
+ return imageByte;
+ }
+
+ void setImageByte(byte[][] imageByte) {
+ this.imageByte = imageByte;
+ }
+
+ static void test() {
+ Random rand = new Random();
+ 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);
+ 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(String[] args) {
+ test();
+ }
+
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngHelperInternal.java
index 1016b1b64..9e64c3eb1 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngHelper.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngHelperInternal.java
@@ -4,37 +4,52 @@ 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.
+ * Some utility static methods for internal use.
* <p>
- * See also <code>FileHelper</code> (if not sandboxed).
+ * Client code should not normally use this class
* <p>
- * Client code should rarely need these methods.
*/
-public class PngHelper {
+public class PngHelperInternal {
/**
* 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
+ /**
+ * UTF-8 is only used for some chunks
+ */
+ public static Charset charsetUTF8 = Charset.forName("UTF-8");
static 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(double d) {
+ return (int) (d * 100000.0 + 0.5);
+ }
+
+ public static double intToDouble100000(int i) {
+ return i / 100000.0;
+ }
+
public static int readByte(InputStream is) {
try {
return is.read();
} catch (IOException e) {
- throw new PngjOutputException(e);
+ throw new PngjInputException("error reading byte", e);
}
}
/**
* -1 if eof
- *
+ *
* PNG uses "network byte order"
*/
public static int readInt2(InputStream is) {
@@ -111,7 +126,7 @@ public class PngHelper {
}
/**
- * guaranteed to read exactly len bytes. throws error if it cant
+ * guaranteed to read exactly len bytes. throws error if it can't
*/
public static void readBytes(InputStream is, byte[] b, int offset, int len) {
if (len == 0)
@@ -121,7 +136,7 @@ public class PngHelper {
while (read < len) {
int n = is.read(b, offset + read, len - read);
if (n < 1)
- throw new RuntimeException("error reading bytes, " + n + " !=" + len);
+ throw new PngjInputException("error reading bytes, " + n + " !=" + len);
read += n;
}
} catch (IOException e) {
@@ -129,6 +144,26 @@ public class PngHelper {
}
}
+ public static void skipBytes(InputStream is, long len) {
+ try {
+ while (len > 0) {
+ 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 (IOException e) {
+ throw new PngjInputException(e);
+ }
+ }
+
public static void writeBytes(OutputStream os, byte[] b) {
try {
os.write(b);
@@ -150,26 +185,8 @@ public class PngHelper {
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>() {
+ @Override
protected CRC32 initialValue() {
return new CRC32();
}
@@ -180,34 +197,74 @@ public class PngHelper {
return crcProvider.get();
}
- static final byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 }; // png magic
+ // / filters
+ public static int filterRowNone(int r) {
+ return (int) (r & 0xFF);
+ }
- public static double resMetersToDpi(long res) {
- return (double) res * 0.0254;
+ public static int filterRowSub(int r, int left) {
+ return ((int) (r - left) & 0xFF);
}
- public static long resDpiToMeters(double dpi) {
- return (long) (dpi / 0.0254 + 0.5);
+ public static int filterRowUp(int r, int up) {
+ return ((int) (r - up) & 0xFF);
}
- public static int doubleToInt100000(double d) {
- return (int) (d * 100000.0 + 0.5);
+ public static int filterRowAverage(int r, int left, int up) {
+ return (r - (left + up) / 2) & 0xFF;
}
- public static double intToDouble100000(int i) {
- return i / 100000.0;
+ public static int filterRowPaeth(int r, int left, int up, int upleft) { // a = left, b = above, c = upper left
+ return (r - filterPaethPredictor(left, up, upleft)) & 0xFF;
}
- public static int clampTo_0_255(int i) {
- return i > 255 ? 255 : (i < 0 ? 0 : i);
+ public static int unfilterRowNone(int r) {
+ return (int) (r & 0xFF);
}
- public static int clampTo_0_65535(int i) {
- return i > 65535 ? 65535 : (i < 0 ? 0 : i);
+ public static int unfilterRowSub(int r, int left) {
+ return ((int) (r + left) & 0xFF);
+ }
+
+ 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;
+ }
+
+ public static int unfilterRowPaeth(int r, int left, int up, 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(PngReader pngr) {
+ pngr.initCrctest();
}
- public static int clampTo_128_127(int x) {
- return x > 127 ? 127 : (x < -128 ? -128 : x);
+ public final static long getCrctestVal(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..cdad09809 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 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(int len, 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(InputStream iStream, int lenFirstChunk, 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); //
+ 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) {
+ 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);
+ 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 {
+ if (ended)
+ return -1; // can happen only when raw reading, see Pngreader.readAndSkipsAllRows()
if (toReadThisChunk == 0)
- throw new RuntimeException("this should not happen");
+ throw new PngjExceptionInternal("this should not happen");
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;
}
@@ -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..411d18819 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngIDatChunkOutputStream.java
@@ -7,23 +7,23 @@ 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);
+ this(outputStream, 0);
}
PngIDatChunkOutputStream(OutputStream outputStream, int size) {
- super(size);
+ super(size > 0 ? size : SIZE_DEFAULT);
this.outputStream = outputStream;
}
@Override
- public final void flushBuffer(byte[] b, int len) {
+ protected final void flushBuffer(byte[] b, int len) {
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..0412beb8c 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(InputStream inputStream, String filenameOrDescription) {
+ this.filename = filenameOrDescription == null ? "" : filenameOrDescription;
+ this.inputStream = inputStream;
+ this.chunksList = new ChunksList(null);
+ this.metadata = new PngMetadata(chunksList);
+ // starts reading: signature
+ 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;
+ int clen = PngHelperInternal.readInt4(inputStream);
+ offset += 4;
+ if (clen != 13)
+ throw new PngjInputException("IDHR chunk len != 13 ?? " + clen);
+ 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;
+ PngChunkIHDR ihdr = (PngChunkIHDR) readChunk(chunkid, clen, false);
+ boolean alpha = (ihdr.getColormodel() & 0x04) != 0;
+ boolean palette = (ihdr.getColormodel() & 0x01) != 0;
+ 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 (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 (Exception e) {
+ }
+ currentChunkGroup = ChunksList.CHUNK_GROUP_6_END;
+ }
+ if (shouldCloseStream) {
+ try {
+ inputStream.close();
+ } catch (Exception e) {
+ throw new PngjInputException("error closing input stream!", e);
+ }
+ }
+ }
+
+ // nbytes: NOT including the filter byte. leaves result in rowb
+ private void unfilterRow(int nbytes) {
+ 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(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] = (byte) (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] = (byte) (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;
+ 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;
+ }
+ 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();
+ 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(byte[] chunkid, int clen, 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));
+ String chunkidstr = ChunkHelper.toString(chunkid);
+ 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 {
+ 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(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(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(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(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(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(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(int nrow) {
+ return readRow(nrow);
+ }
+
+ private void decodeLastReadRowToInt(int[] buffer, 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(byte[] buffer, 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(int rowOffset, int nRows, 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");
+ ImageLines imlines = new ImageLines(imgInfo, SampleType.INT, unpackedMode, rowOffset, nRows, rowStep);
+ if (!interlaced) {
+ for (int j = 0; j < imgInfo.rows; j++) {
+ int bytesread = readRowRaw(j); // read and perhaps discards
+ int mrow = imlines.imageRowToMatrixRowStrict(j);
+ if (mrow >= 0)
+ decodeLastReadRowToInt(imlines.scanlines[mrow], bytesread);
+ }
+ } else { // and now, for something completely different (interlaced)
+ 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++) {
+ int bytesread = readRowRaw(i);
+ int j = deinterlacer.getCurrRowReal();
+ 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(int rowOffset, int nRows, 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");
+ ImageLines imlines = new ImageLines(imgInfo, SampleType.BYTE, unpackedMode, rowOffset, nRows, rowStep);
+ if (!interlaced) {
+ for (int j = 0; j < imgInfo.rows; j++) {
+ int bytesread = readRowRaw(j); // read and perhaps discards
+ int mrow = imlines.imageRowToMatrixRowStrict(j);
+ if (mrow >= 0)
+ decodeLastReadRowToByte(imlines.scanlinesb[mrow], bytesread);
+ }
+ } else { // and now, for something completely different (interlaced)
+ 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++) {
+ int bytesread = readRowRaw(i);
+ int j = deinterlacer.getCurrRowReal();
+ 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
+ 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 (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(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(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(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(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(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(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(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..2f475aab1 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 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
+
+ // 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(OutputStream outputStream, 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
@@ -67,171 +101,156 @@ public class PngWriter {
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);
+ Deflater def = new Deflater(compLevel);
+ def.setStrategy(deflaterStrategy);
+ datStreamDeflated = new DeflaterOutputStream(datStream, def);
+ writeSignatureAndIHDR();
+ writeFirstChunks();
+ }
+
+ 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]++;
}
- 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() {
+ 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();
+ 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
+ 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(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 (byte x : row) { // optimized
+ rowb[j++] = x;
+ }
+ } else { // 16 bitspc
+ for (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(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 (int x : row) { // optimized
+ rowb[j++] = (byte) x;
+ }
+ } else { // 16 bitspc
+ for (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) {
@@ -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(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
+ 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(int rown) {
+ filterRow(rown);
+ try {
+ datStreamDeflated.write(rowbfilter, 0, imgInfo.bytesPerRow + 1);
+ } catch (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] = (byte) 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] = (byte) 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 -= (int) rowbfilter[i];
+ else
+ s += (int) 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)
- */
- 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
+ * TODO: this should be more customizable
*/
private void copyChunks(PngReader reader, int copy_mask, boolean onlyAfterIdat) {
- boolean idatDone = currentChunkGroup >= ChunkList.CHUNK_GROUP_4_IDAT;
+ 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 (PngChunk chunk : reader.getChunksList().getChunks()) {
int group = chunk.getChunkGroup();
- if (group < ChunkList.CHUNK_GROUP_4_IDAT && idatDone)
+ if (group < ChunksList.CHUNK_GROUP_4_IDAT && idatDone)
continue;
boolean copy = false;
if (chunk.crit) {
@@ -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,13 +419,15 @@ 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) {
copyChunks(reader, copy_mask, false);
@@ -438,25 +436,254 @@ public class PngWriter {
/**
* 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) {
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()");
+ double compressed = (double) datStream.getCountFlushed();
+ 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 (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(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(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(int idatMaxSize) {
+ this.idatMaxSize = idatMaxSize;
+ }
+
+ /**
+ * if true, input stream will be closed after ending write
+ * <p>
+ * default=true
+ */
+ public void setShouldCloseStream(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(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(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(ImageLine imgline, 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(int[] newrow) {
+ writeRow(newrow, -1);
+ }
+
+ /**
+ * Alias to writeRowInt
+ *
+ * @see #writeRowInt(int[], int)
+ */
+ public void writeRow(int[] newrow, 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(int[] newrow, 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(byte[] newrow, int rown) {
+ prepareEncodeRow(rown);
+ encodeRowFromByte(newrow);
+ filterAndSend(rown);
+ }
+
+ /**
+ * Writes all the pixels, calling writeRowInt() for each image row
+ */
+ public void writeRowsInt(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(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(boolean useUnpackedMode) {
+ this.unpackedMode = useUnpackedMode;
+ }
+
}
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java b/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java
index 4a45cb5bf..97e24fc73 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/PngjException.java
@@ -2,9 +2,9 @@ package jogamp.opengl.util.pngj;
/**
* Generic exception
- *
+ *
* @author Hernan J Gonzalez
- *
+ *
*/
public class PngjException extends RuntimeException {
private static final long serialVersionUID = 1L;
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..5da70de7b
--- /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(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PngjExceptionInternal(String message) {
+ super(message);
+ }
+
+ public PngjExceptionInternal(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..f68458d19 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;
diff --git a/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java b/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java
index bbec247fb..4516a0886 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/ProgressiveOutputStream.java
@@ -4,10 +4,12 @@ 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) {
this.size = size;
@@ -49,8 +51,8 @@ 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) {
while (forced || count >= size) {
@@ -60,6 +62,7 @@ abstract class ProgressiveOutputStream extends ByteArrayOutputStream {
if (nb == 0)
return;
flushBuffer(buf, nb);
+ countFlushed += nb;
int bytesleft = count - nb;
count = bytesleft;
if (bytesleft > 0)
@@ -67,5 +70,9 @@ abstract class ProgressiveOutputStream extends ByteArrayOutputStream {
}
}
- 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..4e8bf5635 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(String x) {
+ return x.getBytes(PngHelperInternal.charsetLatin1);
+ }
+
+ /**
+ * Converts to String using Latin1 (ISO-8859-1)
+ */
+ public static String toString(byte[] x) {
+ return new String(x, PngHelperInternal.charsetLatin1);
+ }
+
+ /**
+ * Converts to String using Latin1 (ISO-8859-1)
+ */
+ public static String toString(byte[] x, int offset, int len) {
+ return new String(x, offset, len, PngHelperInternal.charsetLatin1);
+ }
+
+ /**
+ * Converts to bytes using UTF-8
+ */
+ public static byte[] toBytesUTF8(String x) {
+ return x.getBytes(PngHelperInternal.charsetUTF8);
+ }
+
+ /**
+ * Converts to string using UTF-8
+ */
+ public static String toStringUTF8(byte[] x) {
+ return new String(x, PngHelperInternal.charsetUTF8);
+ }
+
+ /**
+ * Converts to string using UTF-8
+ */
+ public static String toStringUTF8(byte[] x, int offset, int len) {
+ return new String(x, offset, len, PngHelperInternal.charsetUTF8);
+ }
+
+ /**
+ * critical chunk : first letter is uppercase
+ */
+ public static boolean isCritical(String id) {
+ return (Character.isUpperCase(id.charAt(0)));
+ }
+
+ /**
+ * public chunk: second letter is uppercase
+ */
+ public static boolean isPublic(String id) { //
+ return (Character.isUpperCase(id.charAt(1)));
+ }
+
+ /**
+ * Safe to copy chunk: fourth letter is lower case
+ */
+ public static boolean isSafeToCopy(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(PngChunk c) {
+ return c instanceof PngChunkUNKNOWN;
+ }
+
+ /**
+ * Finds position of null byte in array
+ *
+ * @param b
+ * @return -1 if not found
+ */
+ public static int posNullByte(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(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, getInflater());
+ 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 {
+ synchronized (tmpbuffer) {
+ int len;
+ while ((len = in.read(tmpbuffer)) > 0) {
+ out.write(tmpbuffer, 0, len);
+ }
+ }
+ }
+
+ public static boolean maskMatch(int v, int mask) {
+ return (v & mask) != 0;
+ }
+
+ /**
+ * Returns only the chunks that "match" the predicate
+ *
+ * See also trimList()
+ */
+ public static List<PngChunk> filterList(List<PngChunk> target, ChunkPredicate predicateKeep) {
+ List<PngChunk> result = new ArrayList<PngChunk>();
+ for (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(List<PngChunk> target, ChunkPredicate predicateRemove) {
+ Iterator<PngChunk> it = target.iterator();
+ int cont = 0;
+ while (it.hasNext()) {
+ 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(PngChunk c1, 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(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() {
+ 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() {
+ 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..dcb1958df 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/ChunkRaw.java
@@ -5,24 +5,51 @@ 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
+ /**
+ * @param len
+ * : data len
+ * @param idbytes
+ * : chunk type (deep copied)
+ * @param alloc
+ * : it true, the data array will be allocced
+ */
public ChunkRaw(int len, byte[] idbytes, boolean alloc) {
this.len = len;
System.arraycopy(idbytes, 0, this.idbytes, 0, 4);
@@ -30,54 +57,60 @@ public class ChunkRaw {
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() {
+ 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(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(InputStream is, boolean checkCrc) {
+ PngHelperInternal.readBytes(is, data, 0, len);
+ crcval = PngHelperInternal.readInt4(is);
+ if (checkCrc) {
+ 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..75107d761
--- /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(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() {
+ HashMap<String, Integer> ck = new HashMap<String, Integer>();
+ for (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(PngChunk c) {
+ return c.id.equals(id);
+ }
+ });
+ else
+ return ChunkHelper.filterList(list, new ChunkPredicate() {
+ @Override
+ public boolean match(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(PngChunk chunk, 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) {
+ 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(PngChunk c) {
+ return ChunkHelper.equivalent(c, c2);
+ }
+ });
+ }
+
+ @Override
+ public String toString() {
+ return "ChunkList: read: " + chunks.size();
+ }
+
+ /**
+ * for debugging
+ */
+ public String toStringFull() {
+ StringBuilder sb = new StringBuilder(toString());
+ sb.append("\n Read:\n");
+ for (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..c502e9071
--- /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 HashMap<String, Integer> alreadyWrittenKeys = new HashMap<String, Integer>();
+
+ public ChunksListForWrite(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) {
+ 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(PngChunk c) {
+ return queuedChunks.remove(c);
+ }
+
+ /**
+ * Adds chunk to queue
+ *
+ * Does not check for duplicated or anything
+ *
+ * @param c
+ */
+ public boolean queue(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(PngChunk c, 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(OutputStream os, int currentGroup) {
+ int cont = 0;
+ Iterator<PngChunk> it = queuedChunks.iterator();
+ while (it.hasNext()) {
+ 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() {
+ StringBuilder sb = new StringBuilder(toString());
+ sb.append("\n Written:\n");
+ for (PngChunk chunk : chunks) {
+ sb.append(chunk).append(" G=" + chunk.getChunkGroup() + "\n");
+ }
+ if (!queuedChunks.isEmpty()) {
+ sb.append(" Queued:\n");
+ for (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..6cd86eb98 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,6 +110,35 @@ 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(String chunkId, Class<? extends PngChunk> chunkClass) {
+ factoryMap.put(chunkId, chunkClass);
+ }
+
+ /**
+ * 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(String id) {
+ return factoryMap.containsKey(id);
}
protected PngChunk(String id, ImageInfo imgInfo) {
@@ -55,29 +149,21 @@ 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;
- }
-
+ /**
+ * This factory creates the corresponding chunk and parses the raw chunk.
+ * This is used when reading.
+ */
public static PngChunk factory(ChunkRaw chunk, ImageInfo info) {
PngChunk c = factoryFromId(ChunkHelper.toString(chunk.idbytes), info);
- c.lenori = chunk.len;
- c.parseFromChunk(chunk);
+ c.length = chunk.len;
+ c.parseFromRaw(chunk);
return c;
}
+ /**
+ * Creates one new blank chunk of the corresponding type, according to
+ * factoryMap (PngChunkUNKNOWN if not known)
+ */
public static PngChunk factoryFromId(String cid, ImageInfo info) {
PngChunk chunk = null;
try {
@@ -87,66 +173,112 @@ public abstract class PngChunk {
chunk = constr.newInstance(info);
}
} catch (Exception e) {
- // this can happend for unkown chunks
+ // this can happen for unkown chunks
}
if (chunk == null)
chunk = new PngChunkUNKNOWN(cid, info);
return chunk;
}
- protected ChunkRaw createEmptyChunk(int len, boolean alloc) {
+ protected final ChunkRaw createEmptyChunk(int len, boolean alloc) {
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(T chunk, ImageInfo info) {
+ 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(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(boolean priority) {
+ this.priority = priority;
}
- /** mustGoBeforeXX/After must be overriden - only relevant for ancillary chunks */
- public boolean mustGoBeforeIDAT() {
- return false;
+ final void write(OutputStream os) {
+ 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(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..ea6235432 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkBKGD.java
@@ -1,14 +1,18 @@
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;
@@ -19,43 +23,38 @@ public class PngChunkBKGD extends PngChunk {
}
@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(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);
} 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);
}
}
@@ -71,7 +70,7 @@ public class PngChunkBKGD extends PngChunk {
/**
* Set gray value (0-255 if bitdept=8)
- *
+ *
* @param gray
*/
public void setGray(int gray) {
@@ -88,7 +87,7 @@ public class PngChunkBKGD extends PngChunk {
/**
* Set pallette index
- *
+ *
*/
public void setPaletteIndex(int i) {
if (!imgInfo.indexed)
@@ -104,7 +103,7 @@ public class PngChunkBKGD extends PngChunk {
/**
* Set rgb values
- *
+ *
*/
public void setRGB(int r, int g, int b) {
if (imgInfo.greyscale || imgInfo.indexed)
@@ -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..25a4bf2d6 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkCHRM.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;
-/*
+/**
+ * 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;
@@ -14,46 +19,41 @@ public class PngChunkCHRM extends PngChunk {
private double bluex, bluey;
public PngChunkCHRM(ImageInfo info) {
- super(ChunkHelper.cHRM, info);
- }
-
- @Override
- public boolean mustGoBeforeIDAT() {
- return true;
+ 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(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
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..74640746e 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkGAMA.java
@@ -1,42 +1,42 @@
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;
+ super(ID, info);
}
@Override
- public boolean mustGoBeforePLTE() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_PLTE_AND_IDAT;
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
ChunkRaw c = createEmptyChunk(4, true);
int g = (int) (gamma * 100000 + 0.5);
- PngHelper.writeInt4tobytes(g, c.data, 0);
+ PngHelperInternal.writeInt4tobytes(g, c.data, 0);
return c;
}
@Override
- public void parseFromChunk(ChunkRaw chunk) {
+ public void parseFromRaw(ChunkRaw chunk) {
if (chunk.len != 4)
throw new PngjException("bad chunk " + chunk);
- int g = PngHelper.readInt4fromBytes(chunk.data, 0);
+ int g = PngHelperInternal.readInt4fromBytes(chunk.data, 0);
gamma = ((double) g) / 100000.0;
}
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..6dc3fd9ec 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkHIST.java
@@ -1,50 +1,48 @@
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);
+ 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(ChunkRaw c) {
if (!imgInfo.indexed)
throw new PngjException("only indexed images accept a HIST chunk");
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;
}
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..399577d72 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkICCP.java
@@ -1,31 +1,32 @@
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;
+ super(ID, info);
}
@Override
- public boolean mustGoBeforePLTE() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_PLTE_AND_IDAT;
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
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;
@@ -35,12 +36,12 @@ public class PngChunkICCP extends PngChunk {
}
@Override
- public void parseFromChunk(ChunkRaw chunk) {
+ public void parseFromRaw(ChunkRaw chunk) {
int pos0 = ChunkHelper.posNullByte(chunk.data);
- profileName = new String(chunk.data, 0, pos0, PngHelper.charsetLatin1);
+ profileName = new String(chunk.data, 0, pos0, PngHelperInternal.charsetLatin1);
int comp = (chunk.data[pos0 + 1] & 0xff);
if (comp != 0)
- throw new RuntimeException("bad compression for ChunkTypeICCP");
+ throw new PngjException("bad compression for ChunkTypeICCP");
int compdatasize = chunk.data.length - (pos0 + 2);
compressedProfile = new byte[compdatasize];
System.arraycopy(chunk.data, pos0 + 2, compressedProfile, 0, compdatasize);
@@ -64,7 +65,7 @@ public class PngChunkICCP extends PngChunk {
}
public void setProfileNameAndContent(String name, String profile) {
- setProfileNameAndContent(name, profile.getBytes(PngHelper.charsetLatin1));
+ 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..911513c0d 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIDAT.java
@@ -2,21 +2,36 @@ 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(ImageInfo i, int len, 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(ChunkRaw c) { // does nothing
}
@Override
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..fbec564d8 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkIEND.java
@@ -2,21 +2,33 @@ 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);
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NA;
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
ChunkRaw c = new ChunkRaw(0, ChunkHelper.b_IEND, false);
return c;
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(ChunkRaw c) {
// this is not used
}
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..94bfedd38 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;
@@ -22,16 +28,21 @@ public class PngChunkIHDR extends PngChunk {
// http://www.w3.org/TR/PNG/#11IHDR
//
public PngChunkIHDR(ImageInfo info) {
- super(ChunkHelper.IHDR, info);
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NA;
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
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,18 +53,18 @@ public class PngChunkIHDR extends PngChunk {
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(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);
+ 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
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..ab52d7c90 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkITXT.java
@@ -4,14 +4,17 @@ 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 = "";
@@ -19,24 +22,24 @@ public class PngChunkITXT extends PngChunkTextVar {
// http://www.w3.org/TR/PNG/#11iTXt
public PngChunkITXT(ImageInfo info) {
- super(ChunkHelper.iTXt, 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));
+ 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);
}
@@ -51,7 +54,7 @@ public class PngChunkITXT extends PngChunkTextVar {
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(ChunkRaw c) {
int nullsFound = 0;
int[] nullsIdx = new int[3];
for (int i = 0; i < c.data.length; i++) {
@@ -66,20 +69,21 @@ 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);
+ 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);
}
}
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..057f6c25e
--- /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(String id, 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..a3bab4995
--- /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(ImageInfo info) {
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_IDAT;
+ }
+
+ @Override
+ public ChunkRaw createRawChunk() {
+ 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(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(PngChunk other) {
+ 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(int units) {
+ this.units = units;
+ }
+
+ public long getPosX() {
+ return posX;
+ }
+
+ public void setPosX(long posX) {
+ this.posX = posX;
+ }
+
+ public long getPosY() {
+ return posY;
+ }
+
+ public void setPosY(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..b0a1bb898 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkPHYS.java
@@ -1,44 +1,50 @@
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);
+ super(ID, info);
}
@Override
- public boolean mustGoBeforeIDAT() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_IDAT;
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
ChunkRaw c = createEmptyChunk(9, true);
- PngHelper.writeInt4tobytes((int) pixelsxUnitX, c.data, 0);
- PngHelper.writeInt4tobytes((int) pixelsxUnitY, c.data, 4);
+ 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(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
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..dbf5e53c0 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;
/**
@@ -15,11 +21,16 @@ public class PngChunkPLTE extends PngChunk {
private int[] entries;
public PngChunkPLTE(ImageInfo info) {
- super(ChunkHelper.PLTE, info);
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NA;
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkRaw createRawChunk() {
int len = 3 * nentries;
int[] rgb = new int[3];
ChunkRaw c = createEmptyChunk(len, true);
@@ -33,7 +44,7 @@ public class PngChunkPLTE extends PngChunk {
}
@Override
- public void parseFromChunk(ChunkRaw chunk) {
+ public void parseFromRaw(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));
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..3a490654a 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);
+ 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(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) {
@@ -106,7 +106,7 @@ public class PngChunkSBIT extends PngChunk {
/**
* Set rgb values
- *
+ *
*/
public void setRGB(int r, int g, int b) {
if (imgInfo.greyscale || imgInfo.indexed)
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..2ff65834d 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkSPLT.java
@@ -4,11 +4,17 @@ 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;
@@ -16,35 +22,30 @@ public class PngChunkSPLT extends PngChunk {
private int[] palette; // 5 elements per entry
public PngChunkSPLT(ImageInfo info) {
- super(ChunkHelper.sPLT, 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));
+ ba.write(palName.getBytes(PngHelperInternal.charsetLatin1));
ba.write(0); // separator
ba.write((byte) sampledepth);
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);
@@ -56,7 +57,7 @@ public class PngChunkSPLT extends PngChunk {
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(ChunkRaw c) {
int t = -1;
for (int i = 0; i < c.data.length; i++) { // look for first zero
if (c.data[i] == 0) {
@@ -66,8 +67,8 @@ 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);
palette = new int[nentries * 5];
@@ -75,21 +76,21 @@ public class PngChunkSPLT extends PngChunk {
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;
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..e4d77d40a 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;
@@ -17,28 +22,23 @@ public class PngChunkSRGB extends PngChunk {
private int intent;
public PngChunkSRGB(ImageInfo info) {
- super(ChunkHelper.sRGB, info);
- }
-
- @Override
- public boolean mustGoBeforeIDAT() {
- return true;
+ 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(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;
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..4dc5edec5
--- /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(ImageInfo info) {
+ super(ID, info);
+ }
+
+ @Override
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.BEFORE_IDAT;
+ }
+
+ @Override
+ public ChunkRaw createRawChunk() {
+ ChunkRaw c = createEmptyChunk(1, true);
+ c.data[0] = (byte) mode;
+ return c;
+ }
+
+ @Override
+ public void parseFromRaw(ChunkRaw chunk) {
+ if (chunk.len != 1)
+ throw new PngjException("bad chunk length " + chunk);
+ mode = chunk.data[0];
+ }
+
+ @Override
+ public void cloneDataFromRead(PngChunk other) {
+ 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(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..7df5ba021
--- /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(String id, 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(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ 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..f4c77b4e1
--- /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(String id, ImageInfo info, 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(ChunkRaw c) {
+ throw new PngjException("Non supported for a skipped chunk");
+ }
+
+ @Override
+ public void cloneDataFromRead(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..d97cd63c5 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkTEXT.java
@@ -1,28 +1,40 @@
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 final static String ID = ChunkHelper.tEXt;
+
public PngChunkTEXT(ImageInfo info) {
- super(ChunkHelper.tEXt, info);
+ super(ID, info);
}
@Override
- public ChunkRaw createChunk() {
- if (val.isEmpty() || key.isEmpty())
- return null;
- byte[] b = (key + "\0" + val).getBytes(PngHelper.charsetLatin1);
+ public ChunkRaw createRawChunk() {
+ if (key.isEmpty())
+ throw new PngjException("Text chunk key must be non empty");
+ byte[] b = (key + "\0" + val).getBytes(PngHelperInternal.charsetLatin1);
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(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
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..8f34c78fe 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);
+ super(ID, info);
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NONE;
+ }
+
+ @Override
+ public ChunkRaw createRawChunk() {
ChunkRaw c = createEmptyChunk(7, true);
- PngHelper.writeInt2tobytes(year, c.data, 0);
+ PngHelperInternal.writeInt2tobytes(year, c.data, 0);
c.data[2] = (byte) mon;
c.data[3] = (byte) day;
c.data[4] = (byte) hour;
@@ -28,15 +39,15 @@ public class PngChunkTIME extends PngChunk {
}
@Override
- public void parseFromChunk(ChunkRaw chunk) {
+ public void parseFromRaw(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
@@ -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..867e34861 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(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(ChunkRaw c) {
+ if (imgInfo.greyscale) {
+ gray = PngHelperInternal.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 = PngHelperInternal.readInt2fromBytes(c.data, 0);
+ green = PngHelperInternal.readInt2fromBytes(c.data, 2);
+ blue = PngHelperInternal.readInt2fromBytes(c.data, 4);
+ }
+ }
+
+ @Override
+ public void cloneDataFromRead(PngChunk other) {
+ 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(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;
+ }
+
+ /**
+ * to use when only one pallete index is set as totally transparent
+ */
+ public void setIndexEntryAsTransparent(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..ba3ffc30c 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;
@@ -28,8 +26,8 @@ public abstract class PngChunkTextVar extends PngChunk {
}
@Override
- public boolean allowsMultiple() {
- return true;
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NONE;
}
public static class PngTxtInfo {
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..3803428e6 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkUNKNOWN.java
@@ -2,7 +2,12 @@ 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;
@@ -10,25 +15,25 @@ public class PngChunkUNKNOWN extends PngChunk { // unkown, custom or not
super(id, info);
}
- @Override
- public boolean allowsMultiple() {
- return true;
- }
-
private PngChunkUNKNOWN(PngChunkUNKNOWN c, ImageInfo info) {
super(c.id, info);
System.arraycopy(c.data, 0, data, 0, c.data.length);
}
@Override
- public ChunkRaw createChunk() {
+ public ChunkOrderingConstraint getOrderingConstraint() {
+ return ChunkOrderingConstraint.NONE;
+ }
+
+ @Override
+ public ChunkRaw createRawChunk() {
ChunkRaw p = createEmptyChunk(data.length, false);
p.data = this.data;
return p;
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(ChunkRaw c) {
data = c.data;
}
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..64593eae4 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngChunkZTXT.java
@@ -4,26 +4,32 @@ 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);
+ 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));
+ 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);
+ byte[] textbytes = ChunkHelper.compressBytes(val.getBytes(PngHelperInternal.charsetLatin1), true);
ba.write(textbytes);
byte[] b = ba.toByteArray();
ChunkRaw chunk = createEmptyChunk(b.length, false);
@@ -35,7 +41,7 @@ public class PngChunkZTXT extends PngChunkTextVar {
}
@Override
- public void parseFromChunk(ChunkRaw c) {
+ public void parseFromRaw(ChunkRaw c) {
int nullsep = -1;
for (int i = 0; i < c.data.length; i++) { // look for first zero
if (c.data[i] != 0)
@@ -45,12 +51,12 @@ 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);
+ key = new String(c.data, 0, nullsep, PngHelperInternal.charsetLatin1);
int compmet = (int) 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);
+ val = new String(uncomp, PngHelperInternal.charsetLatin1);
}
@Override
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..139603448 100644
--- a/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java
+++ b/src/jogl/classes/jogamp/opengl/util/pngj/chunks/PngMetadata.java
@@ -1,68 +1,72 @@
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(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, boolean lazyOverwrite) {
+ 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(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);
+ PngChunk c = chunkList.getById1(ChunkHelper.pHYs, true);
if (c == null)
return new double[] { -1, -1 };
else
@@ -76,31 +80,71 @@ public class PngMetadata {
public void setDpi(double x, double y) {
PngChunkPHYS c = new PngChunkPHYS(chunkList.imageInfo);
c.setAsDpi2(x, y);
- setChunk(c, true);
+ queueChunk(c);
}
// //////////// TIME
- public void setTimeNow(int secsAgo) {
+ /**
+ * 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(int secsAgo) {
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) {
+ /**
+ * 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(int yearx, int monx, int dayx, int hourx, int minx, int secx) {
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() : "";
+ 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(String k, String val, boolean useLatin1, boolean compress) {
if (compress && !useLatin1)
throw new PngjException("cannot compress non latin text");
PngChunkTextVar c;
@@ -115,21 +159,83 @@ 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(String k, String val) {
+ return setText(k, val, false, false);
}
- /** tries all text chunks - returns null if not found */
+ /**
+ * 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(String k) {
+ @SuppressWarnings("rawtypes")
+ 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(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;
+ List<? extends PngChunkTextVar> li = getTxtsForKey(k);
+ if (li.isEmpty())
+ return "";
+ StringBuilder t = new StringBuilder();
+ for (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() {
+ 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() {
+ 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>