/** * License: GPL * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package mpicbg.trakem2.util; import ij.process.ByteProcessor; import ij.process.ColorProcessor; import ij.process.FloatProcessor; import ij.process.ImageProcessor; import ij.process.ShortProcessor; /** * * * @author Stephan Saalfeld saalfeld@mpi-cbg.de */ final public class Downsampler { final static public class Entry { final public int width; final public int height; final public byte[][] data; public Entry( final int width, final int height, final byte[][] data ) { this.width = width; this.height = height; this.data = data; } public Entry( final int width, final int height, final int channels ) { this( width, height, new byte[ channels ][ width * height ] ); } } final static public class Pair< A, B > { final public A a; final public B b; public Pair( final A a, final B b ) { this.a = a; this.b = b; } } final static private int averageByte( final int i1, final int i2, final int i3, final int i4, final byte[] data ) { return ( ( data[ i1 ] & 0xff ) + ( data[ i2 ] & 0xff ) + ( data[ i3 ] & 0xff ) + ( data[ i4 ] & 0xff ) ) / 4; } final static private int averageShort( final int i1, final int i2, final int i3, final int i4, final short[] data ) { return ( ( data[ i1 ] & 0xffff ) + ( data[ i2 ] & 0xffff ) + ( data[ i3 ] & 0xffff ) + ( data[ i4 ] & 0xffff ) ) / 4; } final static private float averageFloat( final int i1, final int i2, final int i3, final int i4, final float[] data ) { return ( data[ i1 ] + data[ i2 ] + data[ i3 ] + data[ i4 ] ) / 4; } final static private int averageColorRed( final int rgb1, final int rgb2, final int rgb3, final int rgb4 ) { return ( ( ( rgb1 >> 16 ) & 0xff ) + ( ( rgb2 >> 16 ) & 0xff ) + ( ( rgb3 >> 16 ) & 0xff ) + ( ( rgb4 >> 16 ) & 0xff ) ) / 4; } final static private int averageColorGreen( final int rgb1, final int rgb2, final int rgb3, final int rgb4 ) { return ( ( ( rgb1 >> 8 ) & 0xff ) + ( ( rgb2 >> 8 ) & 0xff ) + ( ( rgb3 >> 8 ) & 0xff ) + ( ( rgb4 >> 8 ) & 0xff ) ) / 4; } final static private int averageColorBlue( final int rgb1, final int rgb2, final int rgb3, final int rgb4 ) { return ( ( rgb1 & 0xff ) + ( rgb2 & 0xff ) + ( rgb3 & 0xff ) + ( rgb4 & 0xff ) ) / 4; } final static private int averageColor( final int i1, final int i2, final int i3, final int i4, final int[] data ) { final int rgb1 = data[ i1 ]; final int rgb2 = data[ i2 ]; final int rgb3 = data[ i3 ]; final int rgb4 = data[ i4 ]; final int red = averageColorRed( rgb1, rgb2, rgb3, rgb4 ); final int green = averageColorGreen( rgb1, rgb2, rgb3, rgb4 ); final int blue = averageColorBlue( rgb1, rgb2, rgb3, rgb4 ); return ( ( ( ( 0xff000000 | red ) << 8 ) | green ) << 8 ) | blue; } final static private int andByte( final int i1, final int i2, final int i3, final int i4, final byte[] data ) { return ( data[ i1 ] & data[ i2 ] & data[ i3 ] & data[ i4 ] & 0xff ); } final static public ByteProcessor downsampleByteProcessor( final ByteProcessor a ) { final int wa = a.getWidth(); final int ha = a.getHeight(); final int wa2 = wa + wa; final int wb = wa / 2; final int hb = ha / 2; final int nb = hb * wb; final ByteProcessor b = new ByteProcessor( wb, hb ); final byte[] aPixels = ( byte[] )a.getPixels(); final byte[] bPixels = ( byte[] )b.getPixels(); for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb ) { final int ya1 = ya + wa; for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb ) { final int xa1 = xa + 1; final int s = averageByte( ya + xa, ya + xa1, ya1 + xa, ya1 + xa1, aPixels ); bPixels[ yb + xb ] = ( byte )s; } } return b; } final static public ByteProcessor downsampleByteProcessor( ByteProcessor a, final int level ) { for ( int i = 0; i < level; ++i ) a = downsampleByteProcessor( a ); return a; } final static public ShortProcessor downsampleShortProcessor( final ShortProcessor a ) { final int wa = a.getWidth(); final int ha = a.getHeight(); final int wa2 = wa + wa; final int wb = wa / 2; final int hb = ha / 2; final int nb = hb * wb; final ShortProcessor b = new ShortProcessor( wb, hb ); final short[] aPixels = ( short[] )a.getPixels(); final short[] bPixels = ( short[] )b.getPixels(); for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb ) { final int ya1 = ya + wa; for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb ) { final int xa1 = xa + 1; final int s = averageShort( ya + xa, ya + xa1, ya1 + xa, ya1 + xa1, aPixels ); bPixels[ yb + xb ] = ( short )s; } } return b; } final static public FloatProcessor downsampleFloatProcessor( FloatProcessor a, final int level ) { for ( int i = 0; i < level; ++i ) a = downsampleFloatProcessor( a ); return a; } final static public FloatProcessor downsampleFloatProcessor( final FloatProcessor a ) { final int wa = a.getWidth(); final int ha = a.getHeight(); final int wa2 = wa + wa; final int wb = wa / 2; final int hb = ha / 2; final int nb = hb * wb; final FloatProcessor b = new FloatProcessor( wb, hb ); final float[] aPixels = ( float[] )a.getPixels(); final float[] bPixels = ( float[] )b.getPixels(); for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb ) { final int ya1 = ya + wa; for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb ) { final int xa1 = xa + 1; final float s = averageFloat( ya + xa, ya + xa1, ya1 + xa, ya1 + xa1, aPixels ); bPixels[ yb + xb ] = s; } } return b; } final static public ColorProcessor downsampleColorProcessor( final ColorProcessor a ) { final int wa = a.getWidth(); final int ha = a.getHeight(); final int wa2 = wa + wa; final int wb = wa / 2; final int hb = ha / 2; final int nb = hb * wb; final ColorProcessor b = new ColorProcessor( wb, hb ); final int[] aPixels = ( int[] )a.getPixels(); final int[] bPixels = ( int[] )b.getPixels(); for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb ) { final int ya1 = ya + wa; for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb ) { final int xa1 = xa + 1; bPixels[ yb + xb ] = averageColor( ya + xa, ya + xa1, ya1 + xa, ya1 + xa1, aPixels ); } } return b; } final static public ColorProcessor downsampleColorProcessor( ColorProcessor a, final int level ) { for ( int i = 0; i < level; ++i ) a = downsampleColorProcessor( a ); return a; } /** * Convenience call for abstract {@link ImageProcessor}. Do not use if you * know the type of the processor to save the time for type checking. * * @param a * @return */ final static public ImageProcessor downsampleImageProcessor( final ImageProcessor a ) { if ( ByteProcessor.class.isInstance( a ) ) return downsampleByteProcessor( ( ByteProcessor )a ); else if ( ShortProcessor.class.isInstance( a ) ) return downsampleShortProcessor( ( ShortProcessor )a ); if ( FloatProcessor.class.isInstance( a ) ) return downsampleFloatProcessor( ( FloatProcessor )a ); if ( ColorProcessor.class.isInstance( a ) ) return downsampleColorProcessor( ( ColorProcessor )a ); else return null; } /** * Convenience call for abstract {@link ImageProcessor}. Do not use if you * know the type of the processor to save the time for type checking. * * @param a * @param level pyramid level in a power of 2 scale pyramid * @return */ final static public ImageProcessor downsampleImageProcessor(ImageProcessor a, final int level ) { for ( int i = 0; i < level; ++i ) a = downsampleImageProcessor( a ); return a; } /** * Create a downsampled version of a {@link ShortProcessor} and the * mapping of its [min,max] range into an unsigned byte array. * * @param a * @return * Pair.a downsampled {@link ShortProcessor} * Pair.b mapped into unsigned byte */ final static public Pair< ShortProcessor, byte[] > downsampleShort( final ShortProcessor a ) { final int wa = a.getWidth(); final int ha = a.getHeight(); final int wa2 = wa + wa; final double min = a.getMin(); final double max = a.getMax(); final double scale = 255.0 / ( max - min ); final int wb = wa / 2; final int hb = ha / 2; final int nb = hb * wb; final ShortProcessor b = new ShortProcessor( wb, hb ); b.setMinAndMax( min, max ); final short[] aPixels = ( short[] )a.getPixels(); final short[] bPixels = ( short[] )b.getPixels(); final byte[] bBytes = new byte[ bPixels.length ]; for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb ) { final int ya1 = ya + wa; for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb ) { final int xa1 = xa + 1; final int yaxa = ya + xa; final int yaxa1 = ya + xa1; final int ya1xa = ya1 + xa; final int ya1xa1 = ya1 + xa1; final int ybxb = yb + xb; final int s = averageShort( yaxa, yaxa1, ya1xa, ya1xa1, aPixels ); bPixels[ ybxb ] = ( short )s; final int sb = ( int )( ( s - min ) * scale + 0.5 ); bBytes[ ybxb ] = ( byte )( sb < 0 ? 0 : sb > 255 ? 255 : sb ); } } return new Pair< ShortProcessor, byte[] >( b, bBytes ); } /** * Create a downsampled version of a {@link FloatProcessor} and the * mapping of its [min,max] range into an unsigned byte array. * * @param a * @return * Pair.a downsampled {@link FloatProcessor} * Pair.b mapped into unsigned byte */ final static public Pair< FloatProcessor, byte[] > downsampleFloat( final FloatProcessor a ) { final int wa = a.getWidth(); final int ha = a.getHeight(); final int wa2 = wa + wa; final double min = a.getMin(); final double max = a.getMax(); final double scale = 255.0 / ( max - min ); final int wb = wa / 2; final int hb = ha / 2; final int nb = hb * wb; final FloatProcessor b = new FloatProcessor( wb, hb ); b.setMinAndMax( min, max ); final float[] aPixels = ( float[] )a.getPixels(); final float[] bPixels = ( float[] )b.getPixels(); final byte[] bBytes = new byte[ bPixels.length ]; for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb ) { final int ya1 = ya + wa; for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb ) { final int xa1 = xa + 1; final int yaxa = ya + xa; final int yaxa1 = ya + xa1; final int ya1xa = ya1 + xa; final int ya1xa1 = ya1 + xa1; final int ybxb = yb + xb; final float s = averageFloat( yaxa, yaxa1, ya1xa, ya1xa1, aPixels ); bPixels[ ybxb ] = s; final int sb = ( int )( ( s - min ) * scale + 0.5 ); bBytes[ ybxb ] = ( byte )( sb < 0 ? 0 : sb > 255 ? 255 : sb ); } } return new Pair< FloatProcessor, byte[] >( b, bBytes ); } /** * Create a downsampled version of a {@link ColorProcessor} and the * mapping of its red, green and blue channels into three unsigned byte * arrays. * * @param a * @return * Pair.a downsampled {@link ColorProcessor} * Pair.b red, green, blue channels as byte[] each */ final static public Pair< ColorProcessor, byte[][] > downsampleColor( final ColorProcessor a ) { final int wa = a.getWidth(); final int ha = a.getHeight(); final int wa2 = wa + wa; final int wb = wa / 2; final int hb = ha / 2; final int nb = hb * wb; final ColorProcessor b = new ColorProcessor( wb, hb ); final int[] aPixels = ( int[] )a.getPixels(); final int[] bPixels = ( int[] )b.getPixels(); final byte[] rBytes = new byte[ bPixels.length ]; final byte[] gBytes = new byte[ bPixels.length ]; final byte[] bBytes = new byte[ bPixels.length ]; for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb ) { final int ya1 = ya + wa; for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb ) { final int xa1 = xa + 1; final int yaxa = ya + xa; final int yaxa1 = ya + xa1; final int ya1xa = ya1 + xa; final int ya1xa1 = ya1 + xa1; final int ybxb = yb + xb; final int rgb1 = aPixels[ yaxa ]; final int rgb2 = aPixels[ yaxa1 ]; final int rgb3 = aPixels[ ya1xa ]; final int rgb4 = aPixels[ ya1xa1 ]; final int red = averageColorRed( rgb1, rgb2, rgb3, rgb4 ); final int green = averageColorGreen( rgb1, rgb2, rgb3, rgb4 ); final int blue = averageColorBlue( rgb1, rgb2, rgb3, rgb4 ); bPixels[ ybxb ] = ( ( ( ( 0xff000000 | red ) << 8 ) | green ) << 8 ) | blue; rBytes[ ybxb ] = ( byte )red; gBytes[ ybxb ] = ( byte )green; bBytes[ ybxb ] = ( byte )blue; } } return new Pair< ColorProcessor, byte[][] >( b, new byte[][]{ rBytes, gBytes, bBytes } ); } /** * Called from a single method below but when not separated into its own method, * inlining does not happen and execution time is doubled. I beg for introducing * inline as a keyword to Java. This magic doesn't make code readable at all. * * @param sOutside * @param yaxa * @param yaxa1 * @param ya1xa * @param ya1xa1 * @param ybxb * @param aAlphaPixels * @param bAlphaPixels * @param bOutsidePixels */ final static private void combineAlphaAndOutside( final int sOutside, final int yaxa, final int yaxa1, final int ya1xa, final int ya1xa1, final int ybxb, final byte[] aAlphaPixels, final byte[] bAlphaPixels, final byte[] bOutsidePixels ) { if ( sOutside == 0xff ) { final int sAlpha = averageByte( yaxa, yaxa1, ya1xa, ya1xa1, aAlphaPixels ); bAlphaPixels[ ybxb ] = ( byte )sAlpha; bOutsidePixels[ ybxb ] = -1; } else { bAlphaPixels[ ybxb ] = 0; bOutsidePixels[ ybxb ] = 0; } } /** * Combine an alpha and outside mask into a downsampled alpha and outside * mask. Those pixels not fully covered in the outside mask are set to 0, * all others to their interpolated value. * * @param aAlpha * @param aOutside * @return */ final static public Pair< ByteProcessor, ByteProcessor > downsampleAlphaAndOutside( final ByteProcessor aAlpha, final ByteProcessor aOutside ) { final int wa = aAlpha.getWidth(); final int ha = aAlpha.getHeight(); final int wa2 = wa + wa; final int wb = wa / 2; final int hb = ha / 2; final int nb = hb * wb; final ByteProcessor bAlpha = new ByteProcessor( wb, hb ); final ByteProcessor bOutside = new ByteProcessor( wb, hb ); final byte[] aAlphaPixels = ( byte[] )aAlpha.getPixels(); final byte[] aOutsidePixels = ( byte[] )aOutside.getPixels(); final byte[] bAlphaPixels = ( byte[] )bAlpha.getPixels(); final byte[] bOutsidePixels = ( byte[] )bOutside.getPixels(); for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb ) { final int ya1 = ya + wa; for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb ) { final int xa1 = xa + 1; final int yaxa = ya + xa; final int yaxa1 = ya + xa1; final int ya1xa = ya1 + xa; final int ya1xa1 = ya1 + xa1; final int ybxb = yb + xb; final int sOutside = andByte( yaxa, yaxa1, ya1xa, ya1xa1, aOutsidePixels ); combineAlphaAndOutside( sOutside, yaxa, yaxa1, ya1xa, ya1xa1, ybxb, aAlphaPixels, bAlphaPixels, bOutsidePixels ); } } return new Pair< ByteProcessor, ByteProcessor >( bAlpha, bOutside ); } /** * Downsample and outside mask. Those pixels not fully covered in the * outside mask are set to 0, all others to 255. * * @param aOutside * @return */ final static public ByteProcessor downsampleOutside( final ByteProcessor aOutside ) { final int wa = aOutside.getWidth(); final int ha = aOutside.getHeight(); final int wa2 = wa + wa; final int wb = wa / 2; final int hb = ha / 2; final int nb = hb * wb; final ByteProcessor bOutside = new ByteProcessor( wb, hb ); final byte[] aOutsidePixels = ( byte[] )aOutside.getPixels(); final byte[] bOutsidePixels = ( byte[] )bOutside.getPixels(); for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb ) { final int ya1 = ya + wa; for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb ) { final int xa1 = xa + 1; final int sOutside = andByte( ya + xa, ya + xa1, ya1 + xa, ya1 + xa1, aOutsidePixels ); if ( sOutside == 0xff ) bOutsidePixels[ yb + xb ] = -1; else bOutsidePixels[ yb + xb ] = 0; } } return bOutside; } }