diff options
author | Sven Gothel <[email protected]> | 2014-02-23 14:51:06 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-02-23 14:51:06 +0100 |
commit | 3352601e0860584509adf2b76f993d03893ded4b (patch) | |
tree | 974fccc8c0eb2f5ad9d4ffd741dfc35869ed67b5 /src/jogl/classes/jogamp/opengl/util | |
parent | f51933f0ebe9ae030c26c066e59a728ce08b8559 (diff) | |
parent | c67de337a8aaf52e36104c3f13e273aa19d21f1f (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')
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>
|