/*
 * License Applicability. Except to the extent portions of this file are
 * made subject to an alternative license as permitted in the SGI Free
 * Software License B, Version 2.0 (the "License"), the contents of this
 * file are subject only to the provisions of the License. You may not use
 * this file except in compliance with the License. You may obtain a copy
 * of the License at Silicon Graphics, Inc., attn: Legal Services, 1600
 * Amphitheatre Parkway, Mountain View, CA 94043-1351, or at:
 *
 * http://oss.sgi.com/projects/FreeB
 *
 * Note that, as provided in the License, the Software is distributed on an
 * "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS
 * DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND
 * CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A
 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
 *
 * NOTE:  The Original Code (as defined below) has been licensed to Sun
 * Microsystems, Inc. ("Sun") under the SGI Free Software License B
 * (Version 1.1), shown above ("SGI License").   Pursuant to Section
 * 3.2(3) of the SGI License, Sun is distributing the Covered Code to
 * you under an alternative license ("Alternative License").  This
 * Alternative License includes all of the provisions of the SGI License
 * except that Section 2.2 and 11 are omitted.  Any differences between
 * the Alternative License and the SGI License are offered solely by Sun
 * and not by SGI.
 *
 * Original Code. The Original Code is: OpenGL Sample Implementation,
 * Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,
 * Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc.
 * Copyright in any portions created by third parties is as indicated
 * elsewhere herein. All Rights Reserved.
 *
 * Additional Notice Provisions: The application programming interfaces
 * established by SGI in conjunction with the Original Code are The
 * OpenGL(R) Graphics System: A Specification (Version 1.2.1), released
 * April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version
 * 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X
 * Window System(R) (Version 1.3), released October 19, 1998. This software
 * was created using the OpenGL(R) version 1.2.1 Sample Implementation
 * published by SGI, but has not been independently verified as being
 * compliant with the OpenGL(R) version 1.2.1 Specification.
 */

package jogamp.opengl.glu.mipmap;

import com.jogamp.opengl.GL;
import java.nio.*;

/**
 *
 * @author  Administrator
 */
public class HalveImage {

  private static final int BOX2 = 2;
  private static final int BOX4 = 4;
  private static final int BOX8 = 8;

  public static void halveImage( final int components, final int width, final int height,
          final ShortBuffer datain, final ShortBuffer dataout ) {
    int i, j, k;
    int newwidth, newheight;
    int delta;
    int t = 0;
    short temp = 0;

    newwidth = width / 2;
    newheight = height /2;
    delta = width * components;

    // Piece of cake
    for( i = 0; i < newheight; i++ ) {
      for( j = 0; j < newwidth; j++ ) {
        for( k = 0; k < components; k++ ) {
          datain.position( t );
          temp = datain.get();
          datain.position( t + components );
          temp += datain.get();
          datain.position( t + delta );
          temp += datain.get();
          datain.position( t + delta + components );
          temp +=datain.get();
          temp += 2;
          temp /= 4;
          dataout.put( temp );
          t++;
        }
        t += components;
      }
      t += delta;
    }
  }

  public static void halveImage_ubyte( final int components, final int width, final int height,
                                      final ByteBuffer datain, final ByteBuffer dataout,
                                      final int element_size, final int ysize, final int group_size ) {
    int i, j, k;
    int newwidth, newheight;
    int s;
    int t;

    // Handle case where there is only 1 column/row
    if( width == 1 || height == 1 ) {
      assert( !( width == 1 && height == 1 ) ); // can't be 1x1
      halve1Dimage_ubyte( components, width, height, datain, dataout, element_size, ysize, group_size );
      return;
    }

    newwidth = width / 2;
    newheight = height / 2;
    s = 0;
    t = 0;

    int temp = 0;
    // piece of cake
    for( i = 0; i < newheight; i++ ) {
      for( j = 0; j < newwidth; j++ ) {
        for( k = 0; k < components; k++ ) {
          datain.position( t );
          temp = ( 0x000000FF & datain.get() );
          datain.position( t + group_size );
          temp += ( 0x000000FF & datain.get() );
          datain.position( t + ysize );
          temp += ( 0x000000FF & datain.get() );
          datain.position( t + ysize + group_size );
          temp += ( 0x000000FF & datain.get() ) + 2;
          dataout.put( (byte)(temp / 4) );
          t += element_size;
        }
        t += group_size;
      }
      t += ysize;
    }
  }

  public static void halve1Dimage_ubyte( final int components, final int width, final int height,
                      final ByteBuffer datain, final ByteBuffer dataout,
                      final int element_size, final int ysize, final int group_size ) {
    int halfWidth = width / 2;
    int halfHeight = height / 2;
    int src = 0;
    int dest = 0;
    int jj;
    int temp = 0;

    assert( width == 1 || height == 1 ); // Must be 1D
    assert( width != height ); // can't be square

    if( height == 1 ) { // 1 row
      assert( width != 1 ); // widthxheight can't be 1x1
      halfHeight = 1;

      for( jj = 0; jj < halfWidth; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          datain.position( src );
          temp = ( 0x000000FF & datain.get() );
          datain.position( src + group_size );
          temp += ( 0x000000FF & datain.get() );
          temp /= 2;
          dataout.put( (byte)temp );
          /*
          dataout.setByte( (byte)(((0x000000FF & datain.setIndexInBytes(src).getByte()) +
                    (0x000000FF & datain.setIndexInBytes( src + group_size ).getByte())) / 2 ) );
           */
          src += element_size;
          //dataout.plusPlus();
          dest++;
        }
        src += group_size; // skip to next 2
      }
      final int padBytes = ysize - ( width * group_size );
      src += padBytes; // for assertion only
    } else if( width == 1 ) { // 1 column
      final int padBytes = ysize - ( width * group_size );
      assert( height != 1 );
      halfWidth = 1;
      // one vertical column with possible pad bytes per row
      // average two at a time
      for( jj = 0; jj < halfHeight; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          datain.position( src );
          temp = ( 0x000000FF & datain.get() );
          datain.position( src + ysize );
          temp += ( 0x000000FF & datain.get() );
          temp /= 2;
          dataout.put( (byte)temp );
          /*
          dataout.setByte( (byte)(((0x000000FF & datain.setIndexInBytes(src).getByte()) +
                    (0x000000FF & datain.setIndexInBytes(src + ysize).getByte()) ) / 2 ) );
           */
          src += element_size;
          //dataout.plusPlus();
          dest++;
        }
        src += padBytes; // add pad bytes, if any, to get to end of row
        src += ysize;
      }
    }
    assert( src == ysize * height );
    assert( dest == components * element_size * halfWidth * halfHeight );
  }

  public static void halveImage_byte( final int components, final int width, final int height,
                    final ByteBuffer datain, final ByteBuffer dataout, final int element_size,
                    final int ysize, final int group_size ) {
    int i, j, k;
    int newwidth, newheight;
    final int s = 0;
    int t = 0;
    byte temp = (byte)0;

    // handle case where there is only 1 column
    if( width == 1 || height == 1 ) {
      assert( !( width == 1 && height == 1 ) );
      halve1Dimage_byte( components, width, height, datain, dataout, element_size,
                                                            ysize, group_size );
      return;
    }

    newwidth = width / 2;
    newheight = height / 2;

    for( i = 0; i < newheight; i++ ) {
      for( j = 0; j < newwidth; j++ ) {
        for( k = 0; k < components; k++ ) {
          datain.position( t );
          temp = datain.get();
          datain.position( t + group_size );
          temp += datain.get();
          datain.position( t + ysize );
          temp += datain.get();
          datain.position( t + ysize + group_size );
          temp += datain.get();
          temp += 2;
          temp /= 4;
          dataout.put( temp );
          t += element_size;
        }
        t += group_size;
      }
      t += ysize;
    }
  }

  public static void halve1Dimage_byte( final int components, final int width, final int height,
                      final ByteBuffer datain, final ByteBuffer dataout,
                      final int element_size, final int ysize, final int group_size ) {
    int halfWidth = width / 2;
    int halfHeight = width / 2;
    int src = 0;
    int dest = 0;
    int jj;
    byte temp = (byte)0;

    assert( width == 1 || height == 1 ); // must be 1D
    assert( width != height ); // can't be square

    if( height == 1 ) { // 1 row
      assert( width != 1 ); // widthxheight can't be 1
      halfHeight = 1;

      for( jj = 0; jj < halfWidth; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          datain.position( src );
          temp = datain.get();
          datain.position( src + group_size );
          temp += datain.get();
          temp /= 2;
          dataout.put( temp );
          src += element_size;
          dest++;
        }
        src += group_size; // skip to next 2
      }
      final int padBytes = ysize - ( width * group_size );
      src += padBytes; // for assert only
    } else if( width == 1 ) { // 1 column
      final int padBytes = ysize - ( width * group_size );
      assert( height != 1 ); // widthxheight can't be 1
      halfWidth = 1;
      // one vertical column with possible pad bytes per row
      // average two at a time

      for( jj = 0; jj < halfHeight; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          datain.position( src );
          temp = datain.get();
          datain.position( src + ysize );
          temp += datain.get();
          temp /= 2;
          src += element_size;
          dest++;
        }
        src += padBytes; // add pad bytes, if any, to get to end of row
        src += ysize;
      }
      assert( src == ysize * height );
    }
    assert( dest == components * element_size * halfWidth * halfHeight );
  }

  public static void halveImage_ushort( final int components, final int width, final int height,
                          final ByteBuffer datain, final ShortBuffer dataout, final int element_size,
                          final int ysize, final int group_size, final boolean myswap_bytes ) {
    int i, j, k;
    final int l;
    int newwidth, newheight;
    final int s = 0;
    int t = 0;
    int temp = 0;
    // handle case where there is only 1 column/row
    if( width == 1 || height == 1 ) {
      assert( !( width == 1 && height == 1 ) ); // can't be 1x1
      halve1Dimage_ushort( components, width, height, datain, dataout, element_size,
                                ysize, group_size, myswap_bytes );
      return;
    }

    newwidth = width / 2;
    newheight = height / 2;

    // Piece of cake
    if( !myswap_bytes ) {
      for( i = 0; i < newheight; i++ ) {
        for( j = 0; j < newwidth; j++ ) {
          for( k = 0; k < components; k++ ) {
            datain.position( t );
            temp = ( 0x0000FFFF & datain.getShort() );
            datain.position( t + group_size );
            temp += ( 0x0000FFFF & datain.getShort() );
            datain.position( t + ysize );
            temp += ( 0x0000FFFF & datain.getShort() );
            datain.position( t + ysize + group_size );
            temp += ( 0x0000FFFF & datain.getShort() );
            dataout.put( (short)( ( temp + 2 ) / 4 ) );
            t += element_size;
          }
          t += group_size;
        }
        t += ysize;
      }
    } else {
      for( i = 0; i < newheight; i++ ) {
        for( j = 0; j < newwidth; j++ ) {
          for( k = 0; k < components; k++ ) {
            datain.position( t );
            temp = ( 0x0000FFFF & Mipmap.GLU_SWAP_2_BYTES( datain.getShort() ) );
            datain.position( t + group_size );
            temp += ( 0x0000FFFF & Mipmap.GLU_SWAP_2_BYTES( datain.getShort() ) );
            datain.position( t + ysize );
            temp += ( 0x0000FFFF & Mipmap.GLU_SWAP_2_BYTES( datain.getShort() ) );
            datain.position( t + ysize + group_size );
            temp += ( 0x0000FFFF & Mipmap.GLU_SWAP_2_BYTES( datain.getShort() ) );
            dataout.put( (short)( ( temp + 2 ) / 4 ) );
            t += element_size;
          }
          t += group_size;
        }
        t += ysize;
      }
    }
  }

  public static void halve1Dimage_ushort( final int components, final int width, final int height,
                      final ByteBuffer datain, final ShortBuffer dataout, final int element_size,
                      final int ysize, final int group_size, final boolean myswap_bytes ) {
    int halfWidth = width / 2;
    int halfHeight = height / 2;
    int src = 0;
    int dest = 0;
    int jj;

    assert( width == 1 || height == 1 ); // must be 1D
    assert( width != height ); // can't be square

    if( height == 1 ) { // 1 row
      assert( width != 1 ); // widthxheight can't be 1
      halfHeight = 1;

      for( jj = 0; jj < halfWidth; jj++ ) {
        int kk;
        for( kk = 0; kk < halfHeight; kk++ ) {
          final int[] ushort = new int[BOX2];
          if( myswap_bytes ) {
            datain.position( src );
            ushort[0] = ( 0x0000FFFF & Mipmap.GLU_SWAP_2_BYTES( datain.getShort() ) );
            datain.position( src + group_size );
            ushort[1] = (0x0000FFFF & Mipmap.GLU_SWAP_2_BYTES( datain.getShort() ) );
          } else {
            datain.position( src );
            ushort[0] = (0x0000FFFF & datain.getShort() );
            datain.position( src + group_size );
            ushort[1] = (0x0000FFFF & datain.getShort() );
          }
          dataout.put( (short)( (ushort[0] + ushort[1]) / 2 ) );
          src += element_size;
          dest += 2;
        }
        src += group_size; // skip to next 2
      }
      final int padBytes = ysize - ( width * group_size );
      src += padBytes; // for assertion only
    } else if( width == 1 ) { // 1 column
      final int padBytes = ysize - ( width * group_size );
      assert( height != 1 ); // widthxheight can't be 1
      halfWidth = 1;
      // one vertical column with possible pad bytes per row
      // average two at a time

      for( jj = 0; jj < halfHeight; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          final int[] ushort = new int[BOX2];
          if( myswap_bytes ) {
            datain.position( src );
            ushort[0] = ( 0x0000FFFF & Mipmap.GLU_SWAP_2_BYTES( datain.getShort() ) );
            datain.position( src + ysize );
            ushort[0] = ( 0x0000FFFF & Mipmap.GLU_SWAP_2_BYTES( datain.getShort() ) );
          } else {
            datain.position( src );
            ushort[0] = ( 0x0000FFFF & datain.getShort() );
            datain.position( src + ysize );
            ushort[1] = ( 0x0000FFFF & datain.getShort() );
          }
          dataout.put( (short)((ushort[0] + ushort[1]) / 2) );
          src += element_size;
          dest += 2;
        }
        src += padBytes; // add pad bytes, if any, to get to end of row
        src += ysize;
      }
      assert( src == ysize * height );
    }
    assert( dest == components * element_size * halfWidth * halfHeight );
  }

  public static void halveImage_short( final int components, final int width, final int height,
                        final ByteBuffer datain, final ShortBuffer dataout, final int element_size,
                        final int ysize, final int group_size, final boolean myswap_bytes ) {
    int i, j, k;
    final int l;
    int newwidth, newheight;
    final int s = 0;
    int t = 0;
    short temp = (short)0;
    // handle case where there is only 1 column/row
    if( width == 1 || height == 1 ) {
      assert( !( width == 1 && height == 1 ) ); // can't be 1x1
      halve1Dimage_short( components, width, height, datain, dataout, element_size,
                            ysize, group_size, myswap_bytes );
      return;
    }

    newwidth = width / 2;
    newheight = height / 2;

    // Piece of cake
    if( !myswap_bytes ) {
      for( i = 0; i < newheight; i++ ) {
        for( j = 0; j < newwidth; j++ ) {
          for( k = 0; k < components; k++ ) {
            datain.position( t );
            temp = datain.getShort();
            datain.position( t + group_size );
            temp += datain.getShort();
            datain.position( t + ysize );
            temp += datain.getShort();
            datain.position( t + ysize + group_size );
            temp += datain.getShort();
            temp += 2;
            temp /= 4;
            dataout.put( temp );
            t += element_size;
          }
          t += group_size;
        }
        t += ysize;
      }
    } else {
      for( i = 0; i < newheight; i++ ) {
        for( j = 0; j < newwidth; j++ ) {
          for( k = 0; k < components; k++ ) {
            final short b;
            final int buf;
            datain.position( t );
            temp = Mipmap.GLU_SWAP_2_BYTES( datain.getShort() );
            datain.position( t + group_size );
            temp += Mipmap.GLU_SWAP_2_BYTES( datain.getShort() );
            datain.position( t + ysize );
            temp += Mipmap.GLU_SWAP_2_BYTES( datain.getShort() );
            datain.position( t + ysize + group_size );
            temp += Mipmap.GLU_SWAP_2_BYTES( datain.getShort() );
            temp += 2;
            temp /= 4;
            dataout.put( temp );
            t += element_size;
          }
          t += group_size;
        }
        t += ysize;
      }
    }
  }

  public static void halve1Dimage_short( final int components, final int width, final int height,
              final ByteBuffer datain, final ShortBuffer dataout, final int element_size, final int ysize,
              final int group_size, final boolean myswap_bytes ) {
    int halfWidth = width / 2;
    int halfHeight = height / 2;
    int src = 0;
    int dest = 0;
    int jj;

    assert( width == 1 || height == 1 ); // must be 1D
    assert( width != height ); // can't be square

    if( height == 1 ) { // 1 row
      assert( width != 1 ); // can't be 1x1
      halfHeight = 1;

      for( jj = 0; jj < halfWidth; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          final short[] sshort = new short[BOX2];
          if( myswap_bytes ) {
            datain.position( src );
            sshort[0] = Mipmap.GLU_SWAP_2_BYTES( datain.getShort() );
            datain.position( src + group_size );
            sshort[1] = Mipmap.GLU_SWAP_2_BYTES( datain.getShort() );
          } else {
            datain.position( src );
            sshort[0] = datain.getShort();
            datain.position( src + group_size );
            sshort[1] = datain.getShort();
          }
          dataout.put( (short)(( sshort[0] + sshort[1] ) / 2) );
          src += element_size;
          dest += 2;
        }
        src += group_size; // skip to next 2
      }
      final int padBytes = ysize - ( width * group_size );
      src += padBytes; // for assertion only
    } else if( width == 1 ) {
      final int padBytes = ysize - ( width * group_size );
      assert( height != 1 );
      halfWidth = 1;
      // one vertical column with possible pad bytes per row
      // average two at a time

      for( jj = 0; jj < halfHeight; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          final short[] sshort = new short[BOX2];
          if( myswap_bytes ) {
            datain.position( src );
            sshort[0] = Mipmap.GLU_SWAP_2_BYTES( datain.getShort() );
            datain.position( src + ysize );
            sshort[1] = Mipmap.GLU_SWAP_2_BYTES( datain.getShort() );
          } else {
            datain.position( src );
            sshort[0] = datain.getShort();
            datain.position( src + ysize );
            sshort[1] = datain.getShort();
          }
          dataout.put( (short)(( sshort[0] + sshort[1] ) / 2) );
          src += element_size;
          dest += 2;
        }
        src += padBytes; // add pad bytes, if any, to get to end of row
        src += ysize;
      }
      assert( src == ysize * height );
    }
    assert( dest == ( components * element_size * halfWidth * halfHeight ) );
  }

  public static void halveImage_uint( final int components, final int width, final int height,
                          final ByteBuffer datain, final IntBuffer dataout, final int element_size,
                          final int ysize, final int group_size, final boolean myswap_bytes ) {
    int i, j, k;
    final int l;
    int newwidth, newheight;
    final int s = 0;
    int t = 0;
    double temp = 0;

    // handle case where there is only 1 column/row
    if( width == 1 || height == 1 ) {
      assert( !( width == 1 && height == 1 ) ); // can't be 1x1
      halve1Dimage_uint( components, width, height, datain, dataout, element_size,
                                ysize, group_size, myswap_bytes );
      return;
    }

    newwidth = width / 2;
    newheight = height / 2;

    // Piece of cake
    if( !myswap_bytes ) {
      for( i = 0; i < newheight; i++ ) {
        for( j = 0; j < newwidth; j++ ) {
          for( k = 0; k < components; k++ ) {
            datain.position( t );
            temp = (0x000000007FFFFFFFL & datain.getInt() );
            datain.position( t + group_size );
            temp += (0x000000007FFFFFFFL & datain.getInt() );
            datain.position( t + ysize );
            temp += (0x000000007FFFFFFFL & datain.getInt() );
            datain.position( t + ysize + group_size );
            temp += (0x000000007FFFFFFFL & datain.getInt() );
            dataout.put( (int)( ( temp / 4 ) + 0.5 ) );
            t += element_size;
          }
          t += group_size;
        }
        t += ysize;
      }
    } else {
      for( i = 0; i < newheight; i++ ) {
        for( j = 0; j < newwidth; j++ ) {
          for( k = 0; k < components; k++ ) {
            // need to cast to double to hold large unsigned ints
            double buf;
            datain.position( t );
            buf = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            datain.position( t + group_size );
            buf += ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            datain.position( t + ysize );
            buf += ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            datain.position( t + ysize + group_size );
            buf += ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            temp /= 4;
            temp += 0.5;
            dataout.put( (int)temp );
            t += element_size;
          }
          t += group_size;
        }
        t += ysize;
      }
    }
  }

  public static void halve1Dimage_uint( final int components, final int width, final int height,
                      final ByteBuffer datain, final IntBuffer dataout, final int element_size, final int ysize,
                      final int group_size, final boolean myswap_bytes ) {
    int halfWidth = width / 2;
    int halfHeight = height / 2;
    int src = 0;
    int dest = 0;
    int jj;

    assert( width == 1 || height == 1 ); // must be 1D
    assert( width != height ); // can't be square

    if( height == 1 ) { // 1 row
      assert( width != 1 ); // widthxheight can't be 1
      halfHeight = 1;

      for( jj = 0; jj < halfWidth; jj++ ) {
        int kk;
        for( kk = 0; kk < halfHeight; kk++ ) {
          final long[] uint = new long[BOX2];
          if( myswap_bytes ) {
            datain.position( src );
            uint[0] = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            datain.position( src + group_size );
            uint[1] = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
          } else {
            datain.position( src );
            uint[0] = ( 0x00000000FFFFFFFF & datain.getInt() );
            datain.position( src + group_size );
            uint[1] = (0x00000000FFFFFFFF & datain.getInt() );
          }
          dataout.put( (int)( ( uint[0] + uint[1] ) / 2.0 ) );
          src += element_size;
          dest += 4;
        }
        src += group_size; // skip to next 2
      }
      final int padBytes = ysize - ( width * group_size );
      src += padBytes; // for assertion only
    } else if( width == 1 ) { // 1 column
      final int padBytes = ysize - ( width * group_size );
      assert( height != 1 ); // widthxheight can't be 1
      halfWidth = 1;
      // one vertical column with possible pad bytes per row
      // average two at a time

      for( jj = 0; jj < halfHeight; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          final long[] uint = new long[BOX2];
          if( myswap_bytes ) {
            datain.position( src );
            uint[0] = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            datain.position( src + group_size );
            uint[0] = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
          } else {
            datain.position( src );
            uint[0] = ( 0x00000000FFFFFFFF & datain.getInt() );
            datain.position( src + ysize );
            uint[1] = ( 0x00000000FFFFFFFF & datain.getInt() );
          }
          dataout.put( (int)( ( uint[0] + uint[1] ) / 2.0 ) );
          src += element_size;
          dest += 4;
        }
        src += padBytes; // add pad bytes, if any, to get to end of row
        src += ysize;
      }
      assert( src == ysize * height );
    }
    assert( dest == components * element_size * halfWidth * halfHeight );
  }

  public static void halveImage_int( final int components, final int width, final int height,
                        final ByteBuffer datain, final IntBuffer dataout, final int element_size,
                        final int ysize, final int group_size, final boolean myswap_bytes ) {
    int i, j, k;
    final int l;
    int newwidth, newheight;
    final int s = 0;
    int t = 0;
    int temp = 0;

    // handle case where there is only 1 column/row
    if( width == 1 || height == 1 ) {
      assert( !( width == 1 && height == 1 ) ); // can't be 1x1
      halve1Dimage_int( components, width, height, datain, dataout, element_size,
                            ysize, group_size, myswap_bytes );
      return;
    }

    newwidth = width / 2;
    newheight = height / 2;

    // Piece of cake
    if( !myswap_bytes ) {
      for( i = 0; i < newheight; i++ ) {
        for( j = 0; j < newwidth; j++ ) {
          for( k = 0; k < components; k++ ) {
            datain.position( t );
            temp = datain.getInt();
            datain.position( t + group_size );
            temp += datain.getInt();
            datain.position( t + ysize );
            temp += datain.getInt();
            datain.position( t + ysize + group_size );
            temp += datain.getInt();
            temp = (int)( ( temp / 4.0f ) + 0.5f );
            dataout.put( temp );
            t += element_size;
          }
          t += group_size;
        }
        t += ysize;
      }
    } else {
      for( i = 0; i < newheight; i++ ) {
        for( j = 0; j < newwidth; j++ ) {
          for( k = 0; k < components; k++ ) {
            long b;
            float buf;
            datain.position( t );
            b = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            buf = b;
            datain.position( t + group_size );
            b = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            buf += b;
            datain.position( t + ysize );
            b = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            buf += b;
            datain.position( t + ysize + group_size );
            b = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            buf += b;
            dataout.put( (int)( ( buf / 4.0f ) + 0.5f ) );
            t += element_size;
          }
          t += group_size;
        }
        t += ysize;
      }
    }
  }

  public static void halve1Dimage_int( final int components, final int width, final int height,
              final ByteBuffer datain, final IntBuffer dataout, final int element_size, final int ysize,
              final int group_size, final boolean myswap_bytes ) {
    int halfWidth = width / 2;
    int halfHeight = height / 2;
    int src = 0;
    int dest = 0;
    int jj;

    assert( width == 1 || height == 1 ); // must be 1D
    assert( width != height ); // can't be square

    if( height == 1 ) { // 1 row
      assert( width != 1 ); // can't be 1x1
      halfHeight = 1;

      for( jj = 0; jj < halfWidth; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          final long[] uint = new long[BOX2];
          if( myswap_bytes ) {
            datain.position( src );
            uint[0] = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            datain.position( src + group_size );
            uint[1] = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
          } else {
            datain.position( src );
            uint[0] = ( 0x00000000FFFFFFFF & datain.getInt() );
            datain.position( src + group_size );
            uint[1] = ( 0x00000000FFFFFFFF & datain.getInt() );
          }
          dataout.put( (int)( ( (float)uint[0] + (float)uint[1] ) / 2.0f) );
          src += element_size;
          dest += 4;
        }
        src += group_size; // skip to next 2
      }
      final int padBytes = ysize - ( width * group_size );
      src += padBytes; // for assertion only
    } else if( width == 1 ) {
      final int padBytes = ysize - ( width * group_size );
      assert( height != 1 );
      halfWidth = 1;
      // one vertical column with possible pad bytes per row
      // average two at a time

      for( jj = 0; jj < halfHeight; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          final long[] uint = new long[BOX2];
          if( myswap_bytes ) {
            datain.position( src );
            uint[0] = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
            datain.position( src + ysize );
            uint[1] = ( 0x00000000FFFFFFFF & Mipmap.GLU_SWAP_4_BYTES( datain.getInt() ) );
          } else {
            datain.position( src );
            uint[0] = ( 0x00000000FFFFFFFF & datain.getInt() );
            datain.position( src + ysize );
            uint[1] = ( 0x00000000FFFFFFFF & datain.getInt() );
          }
          dataout.put( (int)(( (float)uint[0] + (float)uint[1] ) / 2.0f) );
          src += element_size;
          dest += 4;
        }
        src += padBytes; // add pad bytes, if any, to get to end of row
        src += ysize;
      }
      assert( src == ysize * height );
    }
    assert( dest == ( components * element_size * halfWidth * halfHeight ) );
  }

  public static void halveImage_float( final int components, final int width, final int height,
                    final ByteBuffer datain, final FloatBuffer dataout, final int element_size,
                    final int ysize, final int group_size, final boolean myswap_bytes ) {
    int i, j, k;
    final int l;
    int newwidth, newheight;
    final int s = 0;
    int t = 0;
    float temp = 0.0f;
    // handle case where there is only 1 column/row
    if( width == 1 || height == 1 ) {
      assert( !( width == 1 && height == 1 ) ); // can't be 1x1
      halve1Dimage_float( components, width, height, datain, dataout, element_size,
                                              ysize, group_size, myswap_bytes );
      return;
    }

    newwidth = width / 2;
    newheight = height / 2;

    // Piece of cake
    if( !myswap_bytes ) {
      for( i = 0; i < newheight; i++ ) {
        for( j = 0; j < newwidth; j++ ) {
          for( k = 0; k < components; k++ ) {
            datain.position( t );
            temp = datain.getFloat();
            datain.position( t + group_size );
            temp += datain.getFloat();
            datain.position( t + ysize );
            temp += datain.getFloat();
            datain.position( t + ysize + group_size );
            temp /= 4.0f;
            dataout.put( temp );
            t += element_size;
          }
          t += group_size;
        }
        t += ysize;
      }
    } else {
      for( i = 0; i < newheight; i++ ) {
        for( j = 0; j < newwidth; j++ ) {
          for( k = 0; k < components; k++ ) {
            float buf;
            datain.position( t );
            buf = Mipmap.GLU_SWAP_4_BYTES( datain.getFloat() );
            datain.position( t + group_size );
            buf += Mipmap.GLU_SWAP_4_BYTES( datain.getFloat() );
            datain.position( t + ysize );
            buf += Mipmap.GLU_SWAP_4_BYTES( datain.getFloat() );
            datain.position( t + ysize + group_size );
            buf += Mipmap.GLU_SWAP_4_BYTES( datain.getFloat() );
            dataout.put( buf / 4.0f );
            t += element_size;
          }
          t += group_size;
        }
        t += ysize;
      }
    }
  }

  public static void halve1Dimage_float( final int components, final int width, final int height,
              final ByteBuffer datain, final FloatBuffer dataout, final int element_size, final int ysize,
              final int group_size, final boolean myswap_bytes ) {
    int halfWidth = width / 2;
    int halfHeight = height / 2;
    int src = 0;
    int dest = 0;
    int jj;

    assert( width == 1 || height == 1 ); // must be 1D
    assert( width != height ); // can't be square

    if( height == 1 ) { // 1 row
      assert( width != 1 ); // can't be 1x1
      halfHeight = 1;

      for( jj = 0; jj < halfWidth; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          final float[] sfloat = new float[BOX2];
          if( myswap_bytes ) {
            datain.position( src );
            sfloat[0] = Mipmap.GLU_SWAP_4_BYTES( datain.getFloat() );
            datain.position( src + group_size );
            sfloat[1] = Mipmap.GLU_SWAP_4_BYTES( datain.getFloat() );
          } else {
            datain.position( src );
            sfloat[0] = datain.getFloat();
            datain.position( src + group_size );
            sfloat[1] = datain.getFloat();
          }
          dataout.put( (sfloat[0] + sfloat[1]) / 2.0f );
          src += element_size;
          dest += 4;
        }
        src += group_size; // skip to next 2
      }
      final int padBytes = ysize - ( width * group_size );
      src += padBytes; // for assertion only
    } else if( width == 1 ) {
      final int padBytes = ysize - ( width * group_size );
      assert( height != 1 );
      halfWidth = 1;
      // one vertical column with possible pad bytes per row
      // average two at a time

      for( jj = 0; jj < halfHeight; jj++ ) {
        int kk;
        for( kk = 0; kk < components; kk++ ) {
          final float[] sfloat = new float[BOX2];
          if( myswap_bytes ) {
            datain.position( src );
            sfloat[0] = Mipmap.GLU_SWAP_4_BYTES( datain.getFloat() );
            datain.position( src + ysize );
            sfloat[1] = Mipmap.GLU_SWAP_4_BYTES( datain.getFloat() );
          } else {
            datain.position( src );
            sfloat[0] = datain.getFloat();
            datain.position( src + ysize );
            sfloat[1] = datain.getFloat();
          }
          dataout.put( ( sfloat[0] + sfloat[1] ) / 2.0f );
          src += element_size;
          dest += 4;
        }
        src += padBytes; // add pad bytes, if any, to get to end of row
        src += ysize;
      }
      assert( src == ysize * height );
    }
    assert( dest == ( components * element_size * halfWidth * halfHeight ) );
  }

  public static void halveImagePackedPixel( final int components, final Extract extract, final int width,
          final int height, final ByteBuffer datain, final ByteBuffer dataout,
          final int pixelSizeInBytes, final int rowSizeInBytes, final boolean isSwap ) {
    if( width == 1 || height == 1 ) {
      assert( !( width == 1 && height == 1 ) );
      halve1DimagePackedPixel( components, extract, width, height, datain, dataout,
                          pixelSizeInBytes, rowSizeInBytes, isSwap );
      return;
    }
    int ii, jj;

    final int halfWidth = width / 2;
    final int halfHeight = height / 2;
    int src = 0;
    final int padBytes = rowSizeInBytes - ( width * pixelSizeInBytes );
    int outIndex = 0;

    for( ii = 0; ii < halfHeight; ii++ ) {
      for( jj = 0; jj < halfWidth; jj++ ) {
        final float totals[] = new float[4];
        final float extractTotals[][] = new float[BOX4][4];
        int cc;

        datain.position( src );
        extract.extract( isSwap, datain, extractTotals[0] );
        datain.position( src + pixelSizeInBytes );
        extract.extract( isSwap, datain, extractTotals[1] );
        datain.position( src + rowSizeInBytes );
        extract.extract( isSwap, datain, extractTotals[2] );
        datain.position( src + rowSizeInBytes + pixelSizeInBytes );
        extract.extract( isSwap, datain, extractTotals[3] );
        for( cc = 0; cc < components; cc++ ) {
          int kk = 0;
          // grab 4 pixels to average
          totals[cc] = 0.0f;
          for( kk = 0; kk < BOX4; kk++ ) {
            totals[cc] += extractTotals[kk][cc];
          }
          totals[cc] /= BOX4;
        }
        extract.shove( totals, outIndex, dataout );
        outIndex++;
        src += pixelSizeInBytes + pixelSizeInBytes;
      }
      // skip past pad bytes, if any, to get to next row
      src += padBytes;
      src += rowSizeInBytes;
    }
    assert( src == rowSizeInBytes * height );
    assert( outIndex == halfWidth * halfHeight );
  }

  public static void halve1DimagePackedPixel( final int components, final Extract extract, final int width,
              final int height, final ByteBuffer datain, final ByteBuffer dataout,
              final int pixelSizeInBytes, final int rowSizeInBytes, final boolean isSwap ) {
    int halfWidth = width / 2;
    int halfHeight = height / 2;
    int src = 0;
    int jj;

    assert( width == 1 || height == 1 );
    assert( width != height );

    if( height == 1 ) {
      int outIndex = 0;

      assert( width != 1 );
      halfHeight = 1;

      // one horizontal row with possible pad bytes

      for( jj = 0; jj < halfWidth; jj++ ) {
        final float[] totals = new float[4];
        final float[][] extractTotals = new float[BOX2][4];
        int cc;

        datain.position( src );
        extract.extract( isSwap, datain, extractTotals[0] );
        datain.position( src + pixelSizeInBytes );
        extract.extract( isSwap, datain, extractTotals[1] );
        for( cc = 0; cc < components; cc++ ) {
          int kk = 0;
          // grab 4 pixels to average
          totals[cc] = 0.0f;
          for( kk = 0; kk < BOX2; kk++ ) {
            totals[cc] += extractTotals[kk][cc];
          }
          totals[cc] /= BOX2;
        }
        extract.shove( totals, outIndex, dataout );
        outIndex++;
        // skip over to next group of 2
        src += pixelSizeInBytes + pixelSizeInBytes;
      }
      final int padBytes = rowSizeInBytes - ( width * pixelSizeInBytes );
      src += padBytes;

      assert( src == rowSizeInBytes );
      assert( outIndex == halfWidth * halfHeight );
    } else if( width == 1 ) {
      int outIndex = 0;

      assert( height != 1 );
      halfWidth = 1;
      // one vertical volumn with possible pad bytes per row
      // average two at a time

      for( jj = 0; jj < halfHeight; jj++ ) {
        final float[] totals = new float[4];
        final float[][] extractTotals = new float[BOX2][4];
        int cc;
        // average two at a time, instead of four
        datain.position( src );
        extract.extract( isSwap, datain, extractTotals[0] );
        datain.position( src + rowSizeInBytes );
        extract.extract( isSwap, datain, extractTotals[1] );
        for( cc = 0; cc < components; cc++ ) {
          int kk = 0;
          // grab 4 pixels to average
          totals[cc] = 0.0f;
          for( kk = 0; kk < BOX2; kk++ ) {
            totals[cc] += extractTotals[kk][cc];
          }
          totals[cc] /= BOX2;
        }
        extract.shove( totals, outIndex, dataout );
        outIndex++;
        // skip over to next group of 2
        src += rowSizeInBytes + rowSizeInBytes;
      }
      assert( src == rowSizeInBytes );
      assert( outIndex == halfWidth * halfHeight );
    }
  }

  public static void halveImagePackedPixelSlice( final int components, final Extract extract,
          final int width, final int height, final int depth, final ByteBuffer dataIn,
          final ByteBuffer dataOut, final int pixelSizeInBytes, final int rowSizeInBytes,
          final int imageSizeInBytes, final boolean isSwap ) {
    int ii, jj;
    final int halfWidth = width / 2;
    // final int halfHeight = height / 2;
    final int halfDepth = depth / 2;
    int src = 0;
    // final int padBytes = rowSizeInBytes - ( width * pixelSizeInBytes );
    int outIndex = 0;

    assert( (width == 1 || height == 1) && depth >= 2 );

    if( width == height ) {
      assert( width == 1 && height == 1 );
      assert( depth >= 2 );

      for( ii = 0; ii < halfDepth; ii++ ) {
        final float totals[] = new float[4];
        final float extractTotals[][] = new float[BOX2][4];
        int cc;

        dataIn.position( src );
        extract.extract( isSwap, dataIn, extractTotals[0] );
        dataIn.position( src + imageSizeInBytes );
        extract.extract( isSwap, dataIn, extractTotals[1] );

        for( cc = 0; cc < components; cc++ ) {
          int kk;

          // average only 2 pixels since a column
          totals[cc]= 0.0f;
          for( kk = 0; kk < BOX2; kk++ ) {
            totals[cc] += extractTotals[kk][cc];
          }
          totals[cc] /= BOX2;
        } // for cc

        extract.shove( totals, outIndex, dataOut );
        outIndex++;
        // skip over to next group of 2
        src += imageSizeInBytes + imageSizeInBytes;
      } // for ii
    } else if( height == 1 ) {
      assert( width != 1 );

      for( ii = 0; ii < halfDepth; ii++ ) {
        for( jj = 0; jj < halfWidth; jj++ ) {
          final float totals[] = new float[4];
          final float extractTotals[][] = new float[BOX4][4];
          int cc;

          dataIn.position( src );
          extract.extract( isSwap, dataIn, extractTotals[0] );
          dataIn.position( src + pixelSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[1] );
          dataIn.position( src + imageSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[2] );
          dataIn.position( src + pixelSizeInBytes + imageSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[3] );

          for( cc = 0; cc < components; cc++ ) {
            int kk;

            // grab 4 pixels to average
            totals[cc] = 0.0f;
            for( kk = 0; kk < BOX4; kk++ ) {
              totals[cc]+= extractTotals[kk][cc];
            }
            totals[cc]/= BOX4;
          }
          extract.shove( totals, outIndex, dataOut );
          outIndex++;
          // skip over to next horizontal square of 4
          src += imageSizeInBytes + imageSizeInBytes;
        }
      }
    } else if( width == 1 ) {
      assert( height != 1 );

      for( ii = 0; ii < halfDepth; ii++ ) {
        for( jj = 0; jj < halfWidth; jj++ ) {
          final float totals[] = new float[4];
          final float extractTotals[][] = new float[BOX4][4];
          int cc;

          dataIn.position( src );
          extract.extract( isSwap, dataIn, extractTotals[0] );
          dataIn.position( src + rowSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[1] );
          dataIn.position( src + imageSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[2] );
          dataIn.position( src + rowSizeInBytes + imageSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[3] );

          for( cc = 0; cc < components; cc++ ) {
            int kk;

            // grab 4 pixels to average
            totals[cc] = 0.0f;
            for( kk = 0; kk < BOX4; kk++ ) {
              totals[cc]+= extractTotals[kk][cc];
            }
            totals[cc]/= BOX4;
          }
          extract.shove( totals, outIndex, dataOut );
          outIndex++;
          // skip over to next horizontal square of 4
          src += imageSizeInBytes + imageSizeInBytes;
        }
      }
    }
  }

  public static void halveImageSlice( final int components, final ExtractPrimitive extract, final int width,
          final int height, final int depth, final ByteBuffer dataIn, final ByteBuffer dataOut,
          final int elementSizeInBytes, final int groupSizeInBytes, final int rowSizeInBytes,
          final int imageSizeInBytes, final boolean isSwap ) {
    int ii, jj;
    final int halfWidth = width / 2;
    final int halfHeight = height / 2;
    final int halfDepth = depth / 2;
    int src = 0;
    final int padBytes = rowSizeInBytes - ( width * groupSizeInBytes );
    int outIndex = 0;

    assert( (width == 1 || height == 1) && depth >= 2 );

    if( width == height ) {
      assert( width == 1 && height == 1 );
      assert( depth >= 2 );

      for( ii = 0; ii < halfDepth; ii++ ) {
        int cc;
        for( cc = 0; cc < components; cc++ ) {
          final double[] totals = new double[4];
          final double[][] extractTotals = new double[BOX2][4];
          int kk;

          dataIn.position( src );
          extractTotals[0][cc] = extract.extract( isSwap, dataIn );
          dataIn.position( src + imageSizeInBytes );
          extractTotals[1][cc] = extract.extract( isSwap, dataIn );

          // average 2 pixels since only a column
          totals[cc] = 0.0f;
          // totals[red] = extractTotals[0][red] + extractTotals[1][red];
          // totals[red] = red / 2;
          for( kk = 0; kk < BOX2; kk++ ) {
            totals[cc] += extractTotals[kk][cc];
          }
          totals[cc] /= BOX2;

          extract.shove( totals[cc], outIndex, dataOut );
          outIndex++;
          src += elementSizeInBytes;
        } // for cc
        // skip over next group of 2
        src += rowSizeInBytes;
      } // for ii

      assert( src == rowSizeInBytes * height * depth );
      assert( outIndex == halfDepth * components );
    } else if( height == 1 ) {
      assert( width != 1 );

      for( ii = 0; ii < halfDepth; ii++ ) {
        for( jj = 0; jj < halfWidth; jj++ ) {
          int cc;
          for( cc = 0; cc < components; cc++ ) {
            int kk;
            final double totals[] = new double[4];
            final double extractTotals[][] = new double[BOX4][4];

            dataIn.position( src );
            extractTotals[0][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + groupSizeInBytes );
            extractTotals[1][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + imageSizeInBytes );
            extractTotals[2][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + imageSizeInBytes + groupSizeInBytes );
            extractTotals[3][cc] = extract.extract( isSwap, dataIn );

            // grab 4 pixels to average
            totals[cc] = 0.0f;
            // totals[red] = extractTotals[0][red] + extractTotals[1][red] +
            //               extractTotals[2][red] + extractTotals[3][red];
            // totals[red] /= (double)BOX4;
            for( kk = 0; kk < BOX4; kk++ ) {
              totals[cc] += extractTotals[kk][cc];
            }
            totals[cc] /= BOX4;

            extract.shove( totals[cc], outIndex, dataOut );
            outIndex++;
            src += elementSizeInBytes;
          } // for cc
          // skip over to next horizontal square of 4
          src += elementSizeInBytes;
        } // for jj
        src += padBytes;
        src += rowSizeInBytes;
      } // for ii
      assert( src == rowSizeInBytes * height * depth );
      assert( outIndex == halfWidth * halfDepth * components );
    } else if( width == 1 ) {
      assert( height != 1 );

      for( ii = 0; ii < halfDepth; ii++ ) {
        for( jj = 0; jj < halfHeight; jj++ ) {
          int cc;
          for( cc = 0; cc < components; cc++ ) {
            int kk;
            final double totals[] = new double[4];
            final double extractTotals[][] = new double[BOX4][4];

            dataIn.position( src );
            extractTotals[0][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + rowSizeInBytes );
            extractTotals[1][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + imageSizeInBytes );
            extractTotals[2][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + imageSizeInBytes + groupSizeInBytes );
            extractTotals[3][cc] = extract.extract( isSwap, dataIn );


            // grab 4 pixels to average
            totals[cc] = 0.0f;
            // totals[red] = extractTotals[0][red] + extractTotals[1][red] +
            //               extractTotals[2][red] + extractTotals[3][red];
            // totals[red] /= (double)BOX4;
            for( kk = 0; kk < BOX4; kk++ ) {
              totals[cc] += extractTotals[kk][cc];
            }
            totals[cc] /= BOX4;

            extract.shove( totals[cc], outIndex, dataOut );
            outIndex++;
            src += elementSizeInBytes;
          } // for cc
          // skip over to next horizontal square of 4
          src += padBytes;
          src += rowSizeInBytes;
        } // for jj
        src += imageSizeInBytes;
      } // for ii
      assert( src == rowSizeInBytes * height * depth );
      assert( outIndex == halfWidth * halfDepth * components );
    }
  }

  public static void halveImage3D( final int components, final ExtractPrimitive extract,
          final int width, final int height, final int depth, final ByteBuffer dataIn, final ByteBuffer dataOut,
          final int elementSizeInBytes, final int groupSizeInBytes, final int rowSizeInBytes,
          final int imageSizeInBytes, final boolean isSwap ) {
    assert( depth > 1 );

    // horizontal/vertical/onecolumn slice viewed from top
    if( width == 1 || height == 1 ) {
      assert( 1 <= depth );

      halveImageSlice( components, extract, width, height, depth, dataIn, dataOut,
              elementSizeInBytes, groupSizeInBytes, rowSizeInBytes, imageSizeInBytes,
              isSwap );
      return;
    }

    int ii, jj, dd;

    final int halfWidth = width / 2;
    final int halfHeight = height / 2;
    final int halfDepth = depth / 2;
    int src = 0;
    final int padBytes = rowSizeInBytes - ( width * groupSizeInBytes );
    int outIndex = 0;

    for( dd = 0; dd < halfDepth; dd++ ) {
      for( ii = 0; ii < halfHeight; ii++ ) {
        for( jj = 0; jj < halfWidth; jj++ ) {
          int cc;
          for( cc = 0; cc < components; cc++ ) {
            int kk;
            final double totals[] = new double[4];
            final double extractTotals[][] = new double[BOX8][4];

            dataIn.position( src );
            extractTotals[0][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + groupSizeInBytes );
            extractTotals[1][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + rowSizeInBytes );
            extractTotals[2][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + rowSizeInBytes + groupSizeInBytes );
            extractTotals[3][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + imageSizeInBytes );
            extractTotals[4][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + groupSizeInBytes + imageSizeInBytes );
            extractTotals[5][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + rowSizeInBytes + imageSizeInBytes );
            extractTotals[6][cc] = extract.extract( isSwap, dataIn );
            dataIn.position( src + rowSizeInBytes + imageSizeInBytes + groupSizeInBytes );
            extractTotals[7][cc] = extract.extract( isSwap, dataIn );

            totals[cc] = 0.0f;

            for( kk = 0; kk < BOX8; kk++ ) {
              totals[cc] += extractTotals[kk][cc];
            }
            totals[cc] /= BOX8;

            extract.shove( totals[cc], outIndex, dataOut );
            outIndex++;

            src += elementSizeInBytes;
          } // for cc
          // skip over to next square of 4
          src += groupSizeInBytes;
        } // for jj
        // skip past pad bytes, if any, to get to next row
        src += padBytes;
        src += rowSizeInBytes;
      } // for ii
      src += imageSizeInBytes;
    } // for dd
    assert( src == rowSizeInBytes * height * depth );
    assert( outIndex == halfWidth * halfHeight * halfDepth * components );
  }

  public static void halveImagePackedPixel3D( final int components, final Extract extract,
          final int width, final int height, final int depth, final ByteBuffer dataIn,
          final ByteBuffer dataOut, final int pixelSizeInBytes, final int rowSizeInBytes,
          final int imageSizeInBytes, final boolean isSwap ) {
    if( depth == 1 ) {
      assert( 1 <= width && 1 <= height );

      halveImagePackedPixel( components, extract, width, height, dataIn, dataOut,
              pixelSizeInBytes, rowSizeInBytes, isSwap );
      return;
    } else if( width == 1 || height == 1 ) { // a horizontal or vertical slice viewed from top
      assert( 1 <= depth );

      halveImagePackedPixelSlice( components, extract, width, height, depth, dataIn,
              dataOut, pixelSizeInBytes, rowSizeInBytes, imageSizeInBytes, isSwap );
      return;
    }
    int ii, jj, dd;

    final int halfWidth = width / 2;
    final int halfHeight = height / 2;
    final int halfDepth = depth / 2;
    int src = 0;
    final int padBytes = rowSizeInBytes - ( width * pixelSizeInBytes );
    int outIndex = 0;

    for( dd = 0; dd < halfDepth; dd++ ) {
      for( ii = 0; ii < halfHeight; ii++ ) {
        for( jj = 0; jj < halfWidth; jj++ ) {
          final float totals[] = new float[4]; // 4 is max components
          final float extractTotals[][] = new float[BOX8][4];
          int cc;

          dataIn.position( src );
          extract.extract( isSwap, dataIn, extractTotals[0] );
          dataIn.position( src + pixelSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[1] );
          dataIn.position( src + rowSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[2] );
          dataIn.position( src + rowSizeInBytes + pixelSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[3] );
          dataIn.position( src + imageSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[4] );
          dataIn.position( src + pixelSizeInBytes + imageSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[5] );
          dataIn.position( src + rowSizeInBytes + imageSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[6] );
          dataIn.position( src + rowSizeInBytes + pixelSizeInBytes + imageSizeInBytes );
          extract.extract( isSwap, dataIn, extractTotals[7] );

          for( cc = 0; cc < components; cc++ ) {
            int kk;
            // grab 8 pixels to average
            totals[cc] = 0.0f;
            for( kk = 0; kk < BOX8; kk++ ) {
              totals[cc] += extractTotals[kk][cc];
            }
            totals[cc] /= BOX8;
          }
          extract.shove( totals, outIndex, dataOut );
          outIndex++;
          // skip over to next square of 4
          src += pixelSizeInBytes + pixelSizeInBytes;
        }
        // skip past pad bytes, if any, to get to next row
        src += padBytes;
        src += rowSizeInBytes;
      }
      src += imageSizeInBytes;
    }
    assert( src == rowSizeInBytes * height * depth );
    assert( outIndex == halfWidth * halfHeight * halfDepth );
  }
}
