/* * #%L * BSD implementations of Bio-Formats readers and writers * %% * Copyright (C) 2005 - 2015 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDERS 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. * #L% */ package loci.formats; import loci.common.DataTools; /** * A utility class with convenience methods for manipulating images * in primitive array form. * * To work with images in {@link java.awt.image.BufferedImage} form, * use the {@link loci.formats.gui.AWTImageTools} class. * * @author Curtis Rueden ctrueden at wisc.edu */ public final class ImageTools { // -- Constructor -- private ImageTools() { } // -- Image conversion -- /** * Convert an arbitrary primitive type array with 3 samples per pixel to * a 3 x (width * height) byte array. */ public static byte[][] make24Bits(Object pixels, int w, int h, boolean interleaved, boolean reverse) { return make24Bits(pixels, w, h, interleaved, reverse, null, null); } /** * Convert an arbitrary primitive type array with 3 samples per pixel to * a 3 x (width * height) byte array. Scaling is performed according to * the specified minimum and maximum pixel values in the original image. * * If the minimum is null, it is assumed to be 0. * If the maximum is null, it is assumed to be 2^nbits - 1. */ public static byte[][] make24Bits(Object pixels, int w, int h, boolean interleaved, boolean reverse, Double min, Double max) { int[] pix = make24Bits(pixels, w, h, interleaved, min, max); byte[][] rtn = new byte[3][pix.length]; for (int i=0; i<pix.length; i++) { byte r = (byte) ((pix[i] >> 16) & 0xff); rtn[1][i] = (byte) ((pix[i] >> 8) & 0xff); byte b = (byte) (pix[i] & 0xff); rtn[0][i] = reverse ? b : r; rtn[2][i] = reverse ? r : b; } return rtn; } /** * Convert an arbitrary primitive type array with 3 samples per pixel to * an int array, i.e. RGB color with 8 bits per pixel. */ public static int[] make24Bits(Object pixels, int w, int h, boolean interleaved) { return make24Bits(pixels, w, h, interleaved, null, null); } /** * Convert an arbitrary primitive type array with 3 samples per pixel to * an int array, i.e. RGB color with 8 bits per pixel. * * Scaling is performed according to * the specified minimum and maximum pixel values in the original image. * * If the minimum is null, it is assumed to be 0. * If the maximum is null, it is assumed to be 2^nbits - 1. */ public static int[] make24Bits(Object pixels, int w, int h, boolean interleaved, Double min, Double max) { int[] rtn = new int[w * h]; byte[] b = null; if (min == null) min = Double.valueOf(0); double newRange = 255d; // adapted from ImageJ's TypeConverter methods if (pixels instanceof byte[]) b = (byte[]) pixels; else if (pixels instanceof short[]) { if (max == null) max = new Double(0xffff); double range = max.doubleValue() - min.doubleValue(); double mult = newRange / range; short[] s = (short[]) pixels; b = new byte[s.length]; for (int i=0; i<s.length; i++) { b[i] = (byte) Math.abs((s[i] - min.doubleValue()) * mult); } } else if (pixels instanceof int[]) { if (max == null) max = new Double(0xffffffffL); double range = max.doubleValue() - min.doubleValue(); double mult = newRange / range; int[] s = (int[]) pixels; b = new byte[s.length]; for (int i=0; i<s.length; i++) { b[i] = (byte) (Math.abs((s[i] - min.doubleValue()) * mult)); } } else if (pixels instanceof float[]) { if (max == null) max = new Double(Float.MAX_VALUE); double range = max.doubleValue() - min.doubleValue(); double mult = newRange / range; float[] s = (float[]) pixels; b = new byte[s.length]; for (int i=0; i<s.length; i++) { b[i] = (byte) ((s[i] - min.doubleValue()) * mult); } } else if (pixels instanceof double[]) { if (max == null) max = new Double(Double.MAX_VALUE); double range = max.doubleValue() - min.doubleValue(); double mult = newRange / range; double[] s = (double[]) pixels; b = new byte[s.length]; for (int i=0; i<s.length; i++) { b[i] = (byte) ((s[i] - min.doubleValue()) * mult); } } int c = b.length / rtn.length; for (int i=0; i<rtn.length; i++) { byte[] a = new byte[4]; int maxC = Math.min(c, a.length); for (int j=maxC-1; j>=0; j--) { a[j] = b[interleaved ? i * c + j : i + j * w * h]; } if (c == 1) { for (int j=1; j<a.length; j++) { a[j] = a[0]; } } byte tmp = a[0]; a[0] = a[2]; a[2] = tmp; rtn[i] = DataTools.bytesToInt(a, true); } return rtn; } // -- Image manipulation -- /** * Splits the given multi-channel array into a 2D array. * The "reverse" parameter is false if channels are in RGB order, true if * channels are in BGR order. */ public static byte[] splitChannels(byte[] array, int index, int c, int bytes, boolean reverse, boolean interleaved) { return splitChannels(array, null, index, c, bytes, reverse, interleaved, array.length / c); } /** * Splits the given multi-channel array into a 2D array. * The "reverse" parameter is false if channels are in RGB order, true if * channels are in BGR order. If the 'rtn' parameter is not null, the * specified channel will be copied into 'rtn'. * * The 'channelLength' parameter specifies the number of bytes that are * expected to be in a single channel. In many cases, this will match * the value of 'rtn.length', but specifying it separately allows 'rtn' to * be larger than the size of a single channel. */ public static byte[] splitChannels(byte[] array, byte[] rtn, int index, int c, int bytes, boolean reverse, boolean interleaved, int channelLength) { if (c == 1) return array; if (rtn == null) { rtn = new byte[array.length / c]; } if (reverse) index = c - index - 1; if (!interleaved) { System.arraycopy(array, channelLength * index, rtn, 0, channelLength); } else { int next = 0; for (int i=0; i<array.length; i+=c*bytes) { for (int k=0; k<bytes; k++) { if (next < rtn.length) rtn[next] = array[i + index*bytes + k]; next++; } } } return rtn; } /** * Pads (or crops) the byte array to the given width and height. * The image will be centered within the new bounds. */ public static byte[] padImage(byte[] b, boolean interleaved, int c, int oldWidth, int width, int height) { int oldHeight = b.length / (oldWidth * c); byte[] padded = new byte[height * width * c]; int wClip = (width - oldWidth) / 2; int hClip = (height - oldHeight) / 2; int h = height < oldHeight ? height : oldHeight; if (interleaved) { int len = oldWidth < width ? oldWidth : width; if (h == oldHeight) { for (int y=0; y<h*c; y++) { int oldIndex = oldWidth * y; int index = width * y; System.arraycopy(b, oldIndex, padded, index, len); } } else { for (int ch=0; ch<c; ch++) { for (int y=0; y<h; y++) { int oldIndex = oldWidth * ch * oldHeight + oldWidth * y; int index = width * ch * height + width * y; System.arraycopy(b, oldIndex, padded, index, len); } } } } else { int len = oldWidth < width ? oldWidth * c : width * c; for (int oy=0, y=0; oy<oldHeight; oy++, y++) { int oldIndex = oldWidth * c * y; int index = width * c * (y + hClip) + c * wClip; System.arraycopy(b, oldIndex, padded, index, len); } } return padded; } /** * Pads (or crops) the short array to the given width and height. * The image will be centered within the new bounds. */ public static short[] padImage(short[] b, boolean interleaved, int c, int oldWidth, int width, int height) { int oldHeight = b.length / (oldWidth * c); short[] padded = new short[height * width * c]; int wClip = (width - oldWidth) / 2; int hClip = (height - oldHeight) / 2; int h = height < oldHeight ? height : oldHeight; if (interleaved) { int len = oldWidth < width ? oldWidth : width; if (h == oldHeight) { for (int y=0; y<h*c; y++) { int oldIndex = oldWidth * y; int index = width * y; System.arraycopy(b, oldIndex, padded, index, len); } } else { for (int ch=0; ch<c; ch++) { for (int y=0; y<h; y++) { int oldIndex = oldWidth * ch * oldHeight + oldWidth * y; int index = width * ch * height + width * y; System.arraycopy(b, oldIndex, padded, index, len); } } } } else { int len = oldWidth < width ? oldWidth * c : width * c; for (int oy=0, y=0; oy<oldHeight; oy++, y++) { int oldIndex = oldWidth * c * y; int index = width * c * (y + hClip) + c * wClip; System.arraycopy(b, oldIndex, padded, index, len); } } return padded; } /** * Pads (or crops) the int array to the given width and height. * The image will be centered within the new bounds. */ public static int[] padImage(int[] b, boolean interleaved, int c, int oldWidth, int width, int height) { int oldHeight = b.length / (oldWidth * c); int[] padded = new int[height * width * c]; int wClip = (width - oldWidth) / 2; int hClip = (height - oldHeight) / 2; int h = height < oldHeight ? height : oldHeight; if (interleaved) { int len = oldWidth < width ? oldWidth : width; if (h == oldHeight) { for (int y=0; y<h*c; y++) { int oldIndex = oldWidth * y; int index = width * y; System.arraycopy(b, oldIndex, padded, index, len); } } else { for (int ch=0; ch<c; ch++) { for (int y=0; y<h; y++) { int oldIndex = oldWidth * ch * oldHeight + oldWidth * y; int index = width * ch * height + width * y; System.arraycopy(b, oldIndex, padded, index, len); } } } } else { int len = oldWidth < width ? oldWidth * c : width * c; for (int oy=0, y=0; oy<oldHeight; oy++, y++) { int oldIndex = oldWidth * c * y; int index = width * c * (y + hClip) + c * wClip; System.arraycopy(b, oldIndex, padded, index, len); } } return padded; } /** * Pads (or crops) the float array to the given width and height. * The image will be centered within the new bounds. */ public static float[] padImage(float[] b, boolean interleaved, int c, int oldWidth, int width, int height) { int oldHeight = b.length / (oldWidth * c); float[] padded = new float[height * width * c]; int wClip = (width - oldWidth) / 2; int hClip = (height - oldHeight) / 2; int h = height < oldHeight ? height : oldHeight; if (interleaved) { int len = oldWidth < width ? oldWidth : width; if (h == oldHeight) { for (int y=0; y<h*c; y++) { int oldIndex = oldWidth * y; int index = width * y; System.arraycopy(b, oldIndex, padded, index, len); } } else { for (int ch=0; ch<c; ch++) { for (int y=0; y<h; y++) { int oldIndex = oldWidth * ch * oldHeight + oldWidth * y; int index = width * ch * height + width * y; System.arraycopy(b, oldIndex, padded, index, len); } } } } else { int len = oldWidth < width ? oldWidth * c : width * c; for (int oy=0, y=0; oy<oldHeight; oy++, y++) { int oldIndex = oldWidth * c * y; int index = width * c * (y + hClip) + c * wClip; System.arraycopy(b, oldIndex, padded, index, len); } } return padded; } /** * Pads (or crops) the double array to the given width and height. * The image will be centered within the new bounds. */ public static double[] padImage(double[] b, boolean interleaved, int c, int oldWidth, int width, int height) { int oldHeight = b.length / (oldWidth * c); double[] padded = new double[height * width * c]; int wClip = (width - oldWidth) / 2; int hClip = (height - oldHeight) / 2; int h = height < oldHeight ? height : oldHeight; if (interleaved) { int len = oldWidth < width ? oldWidth : width; if (h == oldHeight) { for (int y=0; y<h*c; y++) { int oldIndex = oldWidth * y; int index = width * y; System.arraycopy(b, oldIndex, padded, index, len); } } else { for (int ch=0; ch<c; ch++) { for (int y=0; y<h; y++) { int oldIndex = oldWidth * ch * oldHeight + oldWidth * y; int index = width * ch * height + width * y; System.arraycopy(b, oldIndex, padded, index, len); } } } } else { int len = oldWidth < width ? oldWidth * c : width * c; for (int oy=0, y=0; oy<oldHeight; oy++, y++) { int oldIndex = oldWidth * c * y; int index = width * c * (y + hClip) + c * wClip; System.arraycopy(b, oldIndex, padded, index, len); } } return padded; } /** * Perform autoscaling on the given byte array; * map min to 0 and max to 255. If the number of bytes per pixel is 1, then * nothing happens. */ public static byte[] autoscale(byte[] b, int min, int max, int bpp, boolean little) { if (bpp == 1) return b; byte[] out = new byte[b.length / bpp]; for (int i=0; i<b.length; i+=bpp) { int s = DataTools.bytesToInt(b, i, bpp, little); if (s >= max) s = 255; else if (s <= min) s = 0; else { float diff = max - min; float dist = (s - min) / diff; s = (int)(dist * 255); } out[i / bpp] = (byte) s; } return out; } /** Scan a plane for the channel min and max values. */ public static Double[] scanData(byte[] plane, int bits, boolean littleEndian) { int max = 0; int min = Integer.MAX_VALUE; if (bits <= 8) { for (int j=0; j<plane.length; j++) { if (plane[j] < min) min = plane[j]; if (plane[j] > max) max = plane[j]; } } else if (bits == 16) { for (int j=0; j<plane.length; j+=2) { short s = DataTools.bytesToShort(plane, j, 2, littleEndian); if (s < min) min = s; if (s > max) max = s; } } else if (bits == 32) { for (int j=0; j<plane.length; j+=4) { int s = DataTools.bytesToInt(plane, j, 4, littleEndian); if (s < min) min = s; if (s > max) max = s; } } Double[] rtn = new Double[2]; rtn[0] = new Double(min); rtn[1] = new Double(max); return rtn; } public static byte[] getSubimage(byte[] src, byte[] dest, int originalWidth, int originalHeight, int x, int y, int w, int h, int bpp, int channels, boolean interleaved) { for (int yy=y; yy<y + h; yy++) { for (int xx=x; xx<x + w; xx++) { for (int cc=0; cc<channels; cc++) { int oldNdx = -1, newNdx = -1; if (interleaved) { oldNdx = yy*originalWidth*bpp*channels + xx*bpp*channels + cc*bpp; newNdx = (yy - y)*w*bpp*channels + (xx - x)*bpp*channels + cc*bpp; } else { oldNdx = bpp*(cc*originalWidth*originalHeight + yy*originalWidth + xx); newNdx = bpp*(cc*w*h + (yy - y)*w + (xx - x)); } System.arraycopy(src, oldNdx, dest, newNdx, bpp); } } } return dest; } // -- Indexed color conversion -- /** Converts a LUT and an array of indices into an array of RGB tuples. */ public static byte[][] indexedToRGB(byte[][] lut, byte[] b) { byte[][] rtn = new byte[lut.length][b.length]; for (int i=0; i<b.length; i++) { for (int j=0; j<lut.length; j++) { rtn[j][i] = lut[j][b[i] & 0xff]; } } return rtn; } /** Converts a LUT and an array of indices into an array of RGB tuples. */ public static short[][] indexedToRGB(short[][] lut, byte[] b, boolean le) { short[][] rtn = new short[lut.length][b.length / 2]; for (int i=0; i<b.length/2; i++) { for (int j=0; j<lut.length; j++) { int index = DataTools.bytesToInt(b, i*2, 2, le); rtn[j][i] = lut[j][index]; } } return rtn; } public static byte[] interpolate(short[] s, byte[] buf, int[] bayerPattern, int width, int height, boolean littleEndian) { if (width == 1 && height == 1) { for (int i=0; i<buf.length; i++) { buf[i] = (byte) s[0]; } return buf; } // use linear interpolation to fill in missing components int plane = width * height; for (int row=0; row<height; row++) { for (int col=0; col<width; col++) { //boolean evenRow = (row % 2) == 0; boolean evenCol = (col % 2) == 0; int index = (row % 2) * 2 + (col % 2); boolean needGreen = bayerPattern[index] != 1; boolean needRed = bayerPattern[index] != 0; boolean needBlue = bayerPattern[index] != 2; if (needGreen) { int sum = 0; int ncomps = 0; if (row > 0) { sum += s[plane + (row - 1) * width + col]; ncomps++; } if (row < height - 1) { sum += s[plane + (row + 1) * width + col]; ncomps++; } if (col > 0) { sum += s[plane + row * width + col - 1]; ncomps++; } if (col < width- 1) { sum += s[plane + row*width + col + 1]; ncomps++; } short v = (short) (sum / ncomps); DataTools.unpackBytes(v, buf, row*width*6 + col*6 + 2, 2, littleEndian); } else { DataTools.unpackBytes(s[plane + row*width + col], buf, row*width*6 + col*6 + 2, 2, littleEndian); } if (needRed) { int sum = 0; int ncomps = 0; if (!needBlue) { // four corners if (row > 0) { if (col > 0) { sum += s[(row-1)*width + col - 1]; ncomps++; } if (col < width - 1) { sum += s[(row-1)*width + col + 1]; ncomps++; } } if (row < height - 1) { if (col > 0) { sum += s[(row+1)*width + col - 1]; ncomps++; } if (col < width - 1) { sum += s[(row+1)*width + col + 1]; ncomps++; } } } else if ((evenCol && bayerPattern[index + 1] == 0) || (!evenCol && bayerPattern[index - 1] == 0)) { // horizontal if (col > 0) { sum += s[row*width + col - 1]; ncomps++; } if (col < width - 1) { sum += s[row*width + col + 1]; ncomps++; } } else { // vertical if (row > 0) { sum += s[(row - 1)*width + col]; ncomps++; } if (row < height - 1) { sum += s[(row+1)*width + col]; ncomps++; } } short v = (short) (sum / ncomps); DataTools.unpackBytes(v, buf, row*width*6 + col*6, 2, littleEndian); } else { DataTools.unpackBytes(s[row*width + col], buf, row*width*6 + col*6, 2, littleEndian); } if (needBlue) { int sum = 0; int ncomps = 0; if (!needRed) { // four corners if (row > 0) { if (col > 0) { sum += s[(2*height + row - 1)*width + col - 1]; ncomps++; } if (col < width - 1) { sum += s[(2*height + row-1)*width + col + 1]; ncomps++; } } if (row < height - 1) { if (col > 0) { sum += s[(2*height + row+1)*width + col - 1]; ncomps++; } if (col < width - 1) { sum += s[(2*height + row+1)*width + col + 1]; ncomps++; } } } else if ((evenCol && bayerPattern[index + 1] == 2) || (!evenCol && bayerPattern[index - 1] == 2)) { // horizontal if (col > 0) { sum += s[(2*height + row)*width + col - 1]; ncomps++; } if (col < width - 1) { sum += s[(2*height + row)*width + col + 1]; ncomps++; } } else { // vertical if (row > 0) { sum += s[(2*height + row - 1)*width + col]; ncomps++; } if (row < height - 1) { sum += s[(2*height + row+1)*width + col]; ncomps++; } } short v = (short) (sum / ncomps); DataTools.unpackBytes(v, buf, row*width*6 + col*6 + 4, 2, littleEndian); } else { DataTools.unpackBytes(s[2*plane + row*width + col], buf, row*width*6 + col*6 + 4, 2, littleEndian); } } } return buf; } public static void bgrToRgb(byte[] buf, boolean interleaved, int bpp, int c) { if (c < 3) return; if (interleaved) { for (int i=0; i<buf.length; i+=bpp*c) { for (int b=0; b<bpp; b++) { byte tmp = buf[i + b]; buf[i + b] = buf[i + b + bpp * 2]; buf[i + b + bpp * 2] = tmp; } } } else { byte[] channel = new byte[buf.length / (bpp * c)]; System.arraycopy(buf, 0, channel, 0, channel.length); System.arraycopy(buf, channel.length * 2, buf, 0, channel.length); System.arraycopy(channel, 0, buf, channel.length * 2, channel.length); } } }