package ini.trakem2.persistence; import ij.ImagePlus; import ij.process.ByteProcessor; import ij.process.ColorProcessor; import ij.process.ImageProcessor; import ini.trakem2.display.Patch; import ini.trakem2.imaging.FastIntegralImage; import ini.trakem2.imaging.P; import ini.trakem2.io.ImageSaver; import java.awt.image.BufferedImage; import mpicbg.imglib.algorithm.integral.IntegralImage; import mpicbg.imglib.algorithm.integral.ScaleAreaAveraging2d; import mpicbg.imglib.container.array.Array; import mpicbg.imglib.container.array.ArrayContainerFactory; import mpicbg.imglib.container.basictypecontainer.array.ByteArray; import mpicbg.imglib.function.IntegerTypeConverter; import mpicbg.imglib.image.Image; import mpicbg.imglib.type.numeric.integer.LongType; import mpicbg.imglib.type.numeric.integer.UnsignedByteType; public class IntegralImageMipMaps { /** WARNING modifies the {@code outside} array when both {@code alpha} and {@code outside} are not null, * and when {@code ip} is a {@link ColorProcessor}, will also modify ints int[] pixels * if {@code alpha} or {@code outside} are not null (the alpha channel is or'ed in). * * @param patch * @param ip * @param alpha * @param outside * @param type * @return An array of images represented by an array of byte[], where each is a channel of the image. */ static public final ImageBytes[] create( final Patch patch, ImageProcessor ip, final ByteProcessor alpha, final ByteProcessor outside, final int type) { // Combine alpha and outside // WARNING modifies alpha array final ByteProcessor mask; if (null == alpha) { mask = null == outside ? null : outside; } else if (null == outside) { mask = alpha; } else { final byte[] b1 = (byte[])alpha.getPixels(), b2 = (byte[])outside.getPixels(); for (int i=0; i<b1.length; ++i) { b2[i] = (byte)((b2[i]&0xff) != 255 ? 0 : (b1[i]&0xff)); // 'outside' is a binary mask, qualitative } mask = outside; } // Set min and max /** // No NEED, taken care of by the FSLoader callee function switch (type) { case ImagePlus.COLOR_256: ip = ip.convertToRGB(); //$FALL-THROUGH$ case ImagePlus.GRAY8: case ImagePlus.COLOR_RGB: if (0 != patch.getMin() || 255 != patch.getMax()) { ip = ip.duplicate(); // min,max is destructive on 8-bit channels, so make a copy ip.setMinAndMax(patch.getMin(), patch.getMax()); } break; default: ip.setMinAndMax(patch.getMin(), patch.getMax()); break; } */ // Generate pyramid switch (type) { case ImagePlus.GRAY16: case ImagePlus.GRAY32: ip = ip.convertToByte(true); //$FALL-THROUGH$ case ImagePlus.GRAY8: return fastCreateGRAY8(patch, (ByteProcessor)ip, mask); case ImagePlus.COLOR_256: // Already converted to RGB above case ImagePlus.COLOR_RGB: return fastCreateRGB(patch, (ColorProcessor)ip, mask); } return null; } private static final Image<UnsignedByteType> wrap(final byte[] p, final int[] dims) { final Array<UnsignedByteType,ByteArray> c = new Array<UnsignedByteType,ByteArray>( new ArrayContainerFactory(), new ByteArray(p), dims, 1); final UnsignedByteType t = new UnsignedByteType(c); c.setLinkedType(t); return new Image<UnsignedByteType>(c, t); } private static final int[] blend(final byte[] pi, final byte[] pm) { final int[] p = new int[pi.length]; for (int i=0; i<p.length; ++i) { final int c = (pi[i]&0xff); p[i] = ((pm[i]&0xff) << 24) | (c << 16) | (c << 8) | c; } return p; } /** WARNING will reuse the int[] and return it. */ private static final int[] blend(final int[] pi, final byte[] pm) { for (int i=0; i<pi.length; ++i) { pi[i] = ((pm[i]&0xff) << 24) | pi[i]; } return pi; } private static final int[] blend(final byte[] r, final byte[] g, final byte[] b, final byte[] a) { final int[] p = new int[r.length]; for (int i=0; i<p.length; ++i) { p[i] = ((a[i]&0xff) << 24) | ((r[i]&0xff) << 16) | ((g[i]&0xff) << 8) | (b[i]&0xff); } return p; } private static final int[] blend(final byte[] r, final byte[] g, final byte[] b) { final int[] p = new int[r.length]; for (int i=0; i<p.length; ++i) { p[i] = ((r[i]&0xff) << 16) | ((g[i]&0xff) << 8) | (b[i]&0xff); } return p; } @SuppressWarnings({ "unused", "unchecked", "null" }) private static final BufferedImage[] createGRAY8( final Patch patch, final ByteProcessor ip, final ByteProcessor mask) { final int w = ip.getWidth(); final int h = ip.getHeight(); final int[] dims = new int[]{w, h}; final ScaleAreaAveraging2d<LongType, UnsignedByteType> saai, saam; { // Integral of the image final IntegralImage<UnsignedByteType, LongType> oa = new IntegralImage<UnsignedByteType, LongType>( wrap((byte[])ip.getPixels(), dims), new LongType(), new IntegerTypeConverter<UnsignedByteType, LongType>()); oa.process(); saai = new ScaleAreaAveraging2d<LongType, UnsignedByteType>( oa.getResult(), new UnsignedByteType(), dims); // Integral of the mask, if any if (null != mask) { final IntegralImage<UnsignedByteType, LongType> ma = new IntegralImage<UnsignedByteType, LongType>( wrap((byte[])mask.getPixels(), dims), new LongType(), new IntegerTypeConverter<UnsignedByteType, LongType>()); ma.process(); saam = new ScaleAreaAveraging2d<LongType, UnsignedByteType>(ma.getResult(), new UnsignedByteType(), dims); } else { saam = null; } } // Generate images final BufferedImage[] bis = new BufferedImage[Loader.getHighestMipMapLevel(patch) + 1]; // if (null == saam) { // mask is null // Save images as grayscale bis[0] = ImageSaver.createGrayImage((byte[])ip.getPixels(), w, h); // sharing the byte[] for (int i=1; i<bis.length; i++) { final int K = (int) Math.pow(2, i), wk = w / K, hk = h / K; // An image of the scaled size saai.setOutputDimensions(wk, hk); saai.process(); bis[i] = ImageSaver.createGrayImage(((Array<UnsignedByteType,ByteArray>) saai.getResult().getContainer()).update(null).getCurrentStorageArray(), wk, hk); } } else { // Save images as RGBA, where all 3 color channels are the same bis[0] = ImageSaver.createARGBImage(blend((byte[])ip.getPixels(), (byte[])mask.getPixels()), w, h); for (int i=1; i<bis.length; i++) { final int K = (int) Math.pow(2, i), wk = w / K, hk = h / K; // An image of the scaled size saai.setOutputDimensions(wk, hk); saai.process(); // A mask of the scaled size saam.setOutputDimensions(wk, hk); saam.process(); // bis[i] = ImageSaver.createARGBImage( blend( ((Array<UnsignedByteType,ByteArray>) saai.getResult().getContainer()).update(null).getCurrentStorageArray(), ((Array<UnsignedByteType,ByteArray>) saam.getResult().getContainer()).update(null).getCurrentStorageArray()), wk, hk); } } return bis; } private static final ImageBytes[] fastCreateGRAY8( final Patch patch, final ByteProcessor ip, final ByteProcessor mask) { final int w = ip.getWidth(); final int h = ip.getHeight(); // Integrals final long[] ii = FastIntegralImage.longIntegralImage((byte[])ip.getPixels(), w, h); final long[] mi = null == mask ? null : FastIntegralImage.longIntegralImage((byte[])mask.getPixels(), w, h); // Generate images final ImageBytes[] bis = new ImageBytes[Loader.getHighestMipMapLevel(patch) + 1]; // if (null == mask) { // mask is null // Save images as grayscale bis[0] = new ImageBytes(new byte[][]{(byte[])ip.getPixels()}, ip.getWidth(), ip.getHeight()); for (int i=1; i<bis.length; i++) { final int K = (int) Math.pow(2, i), wk = w / K, hk = h / K; // An image of the scaled size bis[i] = new ImageBytes(new byte[][]{FastIntegralImage.scaleAreaAverage(ii, w+1, h+1, wk, hk)}, wk, hk); } } else { // Save images as RGBA, where all 3 color channels are the same bis[0] = new ImageBytes(new byte[][]{(byte[])ip.getPixels(), (byte[])mask.getPixels()}, ip.getWidth(), ip.getHeight()); for (int i=1; i<bis.length; i++) { final int K = (int) Math.pow(2, i), wk = w / K, hk = h / K; // bis[i] = new ImageBytes( new byte[][]{FastIntegralImage.scaleAreaAverage(ii, w+1, h+1, wk, hk), FastIntegralImage.scaleAreaAverage(mi, w+1, h+1, wk, hk)}, wk, hk); } } return bis; } static private final ScaleAreaAveraging2d<LongType, UnsignedByteType> saa(final byte[] b, final int[] dims) { final IntegralImage<UnsignedByteType, LongType> ii = new IntegralImage<UnsignedByteType, LongType>( wrap(b, dims), new LongType(), new IntegerTypeConverter<UnsignedByteType, LongType>()); ii.process(); return new ScaleAreaAveraging2d<LongType, UnsignedByteType>( ii.getResult(), new UnsignedByteType(), dims); } @SuppressWarnings({ "unused", "unchecked", "null" }) private static final BufferedImage[] createRGB( final Patch patch, final ColorProcessor ip, final ByteProcessor mask) { final int w = ip.getWidth(); final int h = ip.getHeight(); final int[] dims = new int[]{w, h}; final ScaleAreaAveraging2d<LongType, UnsignedByteType> saar, saag, saab, saam; { // Split color channels final int[] p = (int[]) ip.getPixels(); final byte[] r = new byte[p.length], g = new byte[p.length], b = new byte[p.length]; for (int i=0; i<p.length; ++i) { final int a = p[i]; r[i] = (byte)((a >> 16)&0xff); g[i] = (byte)((a >> 8)&0xff); b[i] = (byte)(a&0xff); } // saar = saa(r, dims); saag = saa(g, dims); saab = saa(b, dims); saam = null == mask? null : saa((byte[])mask.getPixels(), dims); } // Generate images final BufferedImage[] bis = new BufferedImage[Loader.getHighestMipMapLevel(patch) + 1]; // if (null == saam) { // mask is null bis[0] = ImageSaver.createARGBImage((int[])ip.getPixels(), w, h); // sharing the int[] pixels for (int i=1; i<bis.length; i++) { final int K = (int) Math.pow(2, i), wk = w / K, hk = h / K; // An image of the scaled size saar.setOutputDimensions(wk, hk); saar.process(); saag.setOutputDimensions(wk, hk); saag.process(); saab.setOutputDimensions(wk, hk); saab.process(); bis[i] = ImageSaver.createARGBImage( blend(((Array<UnsignedByteType,ByteArray>) saar.getResult().getContainer()).update(null).getCurrentStorageArray(), ((Array<UnsignedByteType,ByteArray>) saag.getResult().getContainer()).update(null).getCurrentStorageArray(), ((Array<UnsignedByteType,ByteArray>) saab.getResult().getContainer()).update(null).getCurrentStorageArray()), wk, hk); } } else { // With alpha channel bis[0] = ImageSaver.createARGBImage(blend((int[])ip.getPixels(), (byte[])mask.getPixels()), w, h); // sharing the int[] pixels for (int i=1; i<bis.length; i++) { final int K = (int) Math.pow(2, i), wk = w / K, hk = h / K; // An image of the scaled size saar.setOutputDimensions(wk, hk); saar.process(); saag.setOutputDimensions(wk, hk); saag.process(); saab.setOutputDimensions(wk, hk); saab.process(); saam.setOutputDimensions(wk, hk); saam.process(); bis[i] = ImageSaver.createARGBImage( blend(((Array<UnsignedByteType,ByteArray>) saar.getResult().getContainer()).update(null).getCurrentStorageArray(), ((Array<UnsignedByteType,ByteArray>) saag.getResult().getContainer()).update(null).getCurrentStorageArray(), ((Array<UnsignedByteType,ByteArray>) saab.getResult().getContainer()).update(null).getCurrentStorageArray(), ((Array<UnsignedByteType,ByteArray>) saam.getResult().getContainer()).update(null).getCurrentStorageArray()), wk, hk); } } return bis; } private static final ImageBytes[] fastCreateRGB( final Patch patch, final ColorProcessor ip, final ByteProcessor mask) { final int w = ip.getWidth(); final int h = ip.getHeight(); final long[] ir, ig, ib, im; { // Split color channels final int[] p = (int[]) ip.getPixels(); final byte[] r = new byte[p.length], g = new byte[p.length], b = new byte[p.length]; for (int i=0; i<p.length; ++i) { final int a = p[i]; r[i] = (byte)((a >> 16)&0xff); g[i] = (byte)((a >> 8)&0xff); b[i] = (byte)(a&0xff); } // ir = FastIntegralImage.longIntegralImage(r, w, h); ig = FastIntegralImage.longIntegralImage(g, w, h); ib = FastIntegralImage.longIntegralImage(b, w, h); im = null == mask ? null : FastIntegralImage.longIntegralImage((byte[])mask.getPixels(), w, h); } // Generate images final ImageBytes[] bis = new ImageBytes[Loader.getHighestMipMapLevel(patch) + 1]; // if (null == mask) { bis[0] = new ImageBytes(P.asRGBBytes((int[])ip.getPixels()), ip.getWidth(), ip.getHeight()); for (int i=1; i<bis.length; i++) { final int K = (int) Math.pow(2, i), wk = w / K, hk = h / K; // An image of the scaled size bis[i] = new ImageBytes(new byte[][]{FastIntegralImage.scaleAreaAverage(ir, w+1, h+1, wk, hk), FastIntegralImage.scaleAreaAverage(ig, w+1, h+1, wk, hk), FastIntegralImage.scaleAreaAverage(ib, w+1, h+1, wk, hk)}, wk, hk); } } else { // With alpha channel bis[0] = new ImageBytes(P.asRGBABytes((int[])ip.getPixels(), (byte[])mask.getPixels(), null), ip.getWidth(), ip.getHeight()); for (int i=1; i<bis.length; i++) { final int K = (int) Math.pow(2, i), wk = w / K, hk = h / K; // An image of the scaled size bis[i] = new ImageBytes(new byte[][]{FastIntegralImage.scaleAreaAverage(ir, w+1, h+1, wk, hk), FastIntegralImage.scaleAreaAverage(ig, w+1, h+1, wk, hk), FastIntegralImage.scaleAreaAverage(ib, w+1, h+1, wk, hk), FastIntegralImage.scaleAreaAverage(im, w+1, h+1, wk, hk)}, wk, hk); } } return bis; } }