package ini.trakem2.persistence; import ij.ImagePlus; import ij.process.ByteProcessor; import ij.process.ColorProcessor; import ij.process.FloatProcessor; import ij.process.ImageProcessor; import ij.process.ShortProcessor; import ini.trakem2.display.Patch; import ini.trakem2.imaging.P; import mpicbg.trakem2.util.Downsampler; import mpicbg.trakem2.util.Downsampler.Pair; public final class DownsamplerMipMaps { static private final ImageBytes asBytes(final ByteProcessor bp) { return new ImageBytes(new byte[][]{(byte[])bp.getPixels()}, bp.getWidth(), bp.getHeight()); } static private final ImageBytes asBytes(final ShortProcessor sp) { return asBytes((ByteProcessor)sp.convertToByte(true)); } static private final ImageBytes asBytes(final FloatProcessor fp) { return asBytes((ByteProcessor)fp.convertToByte(true)); } static private final ImageBytes asBytes(final ColorProcessor cp) { return new ImageBytes(P.asRGBBytes((int[])cp.getPixels()), cp.getWidth(), cp.getHeight()); } static private final ImageBytes asBytes(final ByteProcessor bp, final ByteProcessor mask) { return new ImageBytes(new byte[][]{(byte[])bp.getPixels(), (byte[])mask.getPixels()}, bp.getWidth(), bp.getHeight()); } static private final ImageBytes asBytes(final ShortProcessor sp, final ByteProcessor mask) { return asBytes((ByteProcessor)sp.convertToByte(true), mask); } static private final ImageBytes asBytes(final FloatProcessor fp, final ByteProcessor mask) { return asBytes((ByteProcessor)fp.convertToByte(true), mask); } static private final ImageBytes asBytes(final ColorProcessor cp, final ByteProcessor mask) { return new ImageBytes(P.asRGBABytes((int[])cp.getPixels(), (byte[])mask.getPixels(), null), cp.getWidth(), cp.getHeight()); } // TODO the int[] should be preserved for color images static public final ImageBytes[] create( final Patch patch, final int type, final ImageProcessor ip, final ByteProcessor alpha, final ByteProcessor outside) { // Create pyramid final ImageBytes[] p = new ImageBytes[Loader.getHighestMipMapLevel(patch) + 1]; if (null == alpha && null == outside) { int i = 1; switch (type) { case ImagePlus.GRAY8: ByteProcessor bp = (ByteProcessor)ip; p[0] = asBytes(bp); while (i < p.length) { bp = Downsampler.downsampleByteProcessor(bp); p[i++] = asBytes(bp); } break; case ImagePlus.GRAY16: ShortProcessor sp = (ShortProcessor)ip; p[0] = asBytes(sp); Pair<ShortProcessor, byte[]> rs; while (i < p.length) { rs = Downsampler.downsampleShort(sp); sp = rs.a; p[i++] = new ImageBytes(new byte[][]{rs.b}, sp.getWidth(), sp.getHeight()); } break; case ImagePlus.GRAY32: FloatProcessor fp = (FloatProcessor)ip; p[0] = asBytes(fp); Pair<FloatProcessor, byte[]> rf; while (i < p.length) { rf = Downsampler.downsampleFloat(fp); fp = rf.a; p[i++] = new ImageBytes(new byte[][]{rf.b}, fp.getWidth(), fp.getHeight()); } break; case ImagePlus.COLOR_RGB: ColorProcessor cp = (ColorProcessor)ip; p[0] = asBytes(cp); // TODO the int[] could be reused Pair<ColorProcessor, byte[][]> rc; while (i < p.length) { rc = Downsampler.downsampleColor(cp); cp = rc.a; p[i++] = new ImageBytes(rc.b, cp.getWidth(), cp.getHeight()); } break; } } else { // Alpha channel final ByteProcessor[] masks = new ByteProcessor[p.length]; if (null != alpha && null != outside) { // Use both alpha and outside: final byte[] b1 = (byte[])alpha.getPixels(), b2 = (byte[])outside.getPixels(); for (int i=0; i<b1.length; ++i) { b1[i] = b2[i] != -1 ? 0 : b1[i]; // 'outside' is a binary mask, qualitative. -1 means 255 } masks[0] = alpha; // int i = 1; Pair<ByteProcessor,ByteProcessor> pair; ByteProcessor a = alpha, o = outside; while (i < p.length) { pair = Downsampler.downsampleAlphaAndOutside(a, o); a = pair.a; o = pair.b; masks[i] = a; // o is already combined into it ++i; } } else { // Only one of the two is not null: if (null == alpha) { masks[0] = outside; int i = 1; while (i < p.length) { masks[i] = Downsampler.downsampleOutside(masks[i-1]); ++i; } } else { masks[0] = alpha; int i = 1; while (i < p.length) { masks[i] = Downsampler.downsampleByteProcessor(masks[i-1]); ++i; } } } // Image channels int i = 1; switch (type) { case ImagePlus.GRAY8: ByteProcessor bp = (ByteProcessor)ip; p[0] = asBytes(bp, masks[0]); while (i < p.length) { bp = Downsampler.downsampleByteProcessor(bp); p[i] = asBytes(bp, masks[i]); ++i; } break; case ImagePlus.GRAY16: ShortProcessor sp = (ShortProcessor)ip; p[0] = asBytes(sp, masks[0]); while (i < p.length) { final Pair< ShortProcessor, byte[] > rs = Downsampler.downsampleShort(sp); sp = rs.a; p[i] = new ImageBytes(new byte[][]{rs.b, (byte[])masks[i].getPixels()}, sp.getWidth(), sp.getHeight()); ++i; } break; case ImagePlus.GRAY32: FloatProcessor fp = (FloatProcessor)ip; p[0] = asBytes(fp, masks[0]); while (i < p.length) { final Pair< FloatProcessor, byte[] > rs = Downsampler.downsampleFloat( fp ); fp = rs.a; p[i] = new ImageBytes(new byte[][]{rs.b, (byte[])masks[i].getPixels()}, fp.getWidth(), fp.getHeight()); ++i; } break; case ImagePlus.COLOR_RGB: ColorProcessor cp = (ColorProcessor)ip; p[0] = asBytes(cp, masks[0]); // TODO the int[] could be reused while (i < p.length) { final Pair< ColorProcessor, byte[][] > rs = Downsampler.downsampleColor( cp ); cp = rs.a; final byte[][] rgb = rs.b; p[i] = new ImageBytes(new byte[][]{rgb[0], rgb[1], rgb[2], (byte[])masks[i].getPixels()}, cp.getWidth(), cp.getHeight()); ++i; } break; } } return p; } }