/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2014 GeoSolutions * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package it.geosolutions.jaiext.utilities; import java.awt.Rectangle; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; import java.awt.image.DataBufferUShort; import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; import java.text.ChoiceFormat; import javax.media.jai.PixelAccessor; import javax.media.jai.UnpackedImageData; import com.sun.media.imageioimpl.common.PackageUtil; public class ImageUtilities { /** * {@code true} if JAI media lib is available. */ private static final boolean mediaLibAvailable; static { // do we wrappers at hand? boolean mediaLib = false; Class mediaLibImage = null; try { mediaLibImage = Class.forName("com.sun.medialib.mlib.Image"); } catch (ClassNotFoundException e) { } mediaLib = (mediaLibImage != null); // npw check if we either wanted to disable explicitly and if we // installed the native libs if (mediaLib) { try { // explicit disable mediaLib = !Boolean.getBoolean("com.sun.media.jai.disableMediaLib"); // native libs installed if (mediaLib) { final Class mImage = mediaLibImage; mediaLib = AccessController.doPrivileged(new PrivilegedAction<Boolean>() { public Boolean run() { try { // get the method final Class params[] = {}; Method method = mImage.getDeclaredMethod("isAvailable", params); // invoke final Object paramsObj[] = {}; final Object o = mImage.newInstance(); return (Boolean) method.invoke(o, paramsObj); } catch (Throwable e) { return false; } } }); } } catch (Throwable e) { // Because the property com.sun.media.jai.disableMediaLib isn't // defined as public, the users shouldn't know it. In most of // the cases, it isn't defined, and thus no access permission // is granted to it in the policy file. When JAI is utilized in // a security environment, AccessControlException will be // thrown. // In this case, we suppose that the users would like to use // medialib accelaration. So, the medialib won't be disabled. // The fix of 4531501 mediaLib = false; } } mediaLibAvailable = mediaLib; } /** * Tells me whether or not the native libraries for JAI are active or not. * * @return <code>false</code> in case the JAI native libs are not in the path, <code>true</code> otherwise. */ public static boolean isMediaLibAvailable() { return mediaLibAvailable; } /** * Tells me whether or not the native libraries for JAI/ImageIO are active or not. * * @return <code>false</code> in case the JAI/ImageIO native libs are not in the path, <code>true</code> otherwise. */ public static boolean isCLibAvailable() { return PackageUtil.isCodecLibAvailable(); } /** * Returns the next or previous representable number. If {@code amount} is equals to {@code 0}, then this method returns the {@code value} * unchanged. Otherwise, The operation performed depends on the specified {@code type}: * <ul> * <li> * <p> * If the {@code type} is {@link Double}, then this method is equivalent to invoking {@link #previous(double)} if {@code amount} is equals to * {@code -1}, or invoking {@link #next(double)} if {@code amount} is equals to {@code +1}. If {@code amount} is smaller than {@code -1} or * greater than {@code +1}, then this method invokes {@link #previous(double)} or {@link #next(double)} in a loop for {@code abs(amount)} times. * </p> * </li> * * <li> * <p> * If the {@code type} is {@link Float}, then this method is equivalent to invoking {@link #previous(float)} if {@code amount} is equals to * {@code -1}, or invoking {@link #next(float)} if {@code amount} is equals to {@code +1}. If {@code amount} is smaller than {@code -1} or greater * than {@code +1}, then this method invokes {@link #previous(float)} or {@link #next(float)} in a loop for {@code abs(amount)} times. * </p> * </li> * * <li> * <p> * If the {@code type} is an {@linkplain #isInteger integer}, then invoking this method is equivalent to computing {@code value + amount}. * </p> * </li> * </ul> * * @param type The type. Should be the class of {@link Double}, {@link Float} , {@link Long}, {@link Integer}, {@link Short} or {@link Byte} . * @param value The number to rool. * @param amount -1 to return the previous representable number, +1 to return the next representable number, or 0 to return the number with no * change. * @return One of previous or next representable number as a {@code double}. * @throws IllegalArgumentException if {@code type} is not one of supported types. */ public static double rool(final Class type, double value, int amount) throws IllegalArgumentException { if (Double.class.equals(type)) { if (amount < 0) { do { value = previous(value); } while (++amount != 0); } else if (amount != 0) { do { value = next(value); } while (--amount != 0); } return value; } if (Float.class.equals(type)) { float vf = (float) value; if (amount < 0) { do { vf = next(vf, false); } while (++amount != 0); } else if (amount != 0) { do { vf = next(vf, true); } while (--amount != 0); } return vf; } if (isInteger(type)) { return value + amount; } throw new IllegalArgumentException("Unsupported DataType: " + type); } /** * Returns {@code true} if the specified {@code type} is one of integer types. Integer types includes {@link Long}, {@link Integer}, {@link Short} * and {@link Byte}. * * @param type The type to test (may be {@code null}). * @return {@code true} if {@code type} is the class {@link Long}, {@link Integer}, {@link Short} or {@link Byte}. * * @deprecated Moved to {@link Classes}. */ @Deprecated public static boolean isInteger(final Class<?> type) { return type != null && Long.class.equals(type) || Integer.class.equals(type) || Short.class.equals(type) || Byte.class.equals(type); } /** * Finds the least double greater than <var>f</var>. If {@code NaN}, returns same value. * * @see java.text.ChoiceFormat#nextDouble * * @todo Remove this method when we will be allowed to use Java 6. */ public static double next(final double f) { return ChoiceFormat.nextDouble(f); } /** * Finds the greatest double less than <var>f</var>. If {@code NaN}, returns same value. * * @see java.text.ChoiceFormat#previousDouble * * @todo Remove this method when we will be allowed to use Java 6. */ public static double previous(final double f) { return ChoiceFormat.previousDouble(f); } private static float next(final float f, final boolean positive) { final int SIGN = 0x80000000; final int POSITIVEINFINITY = 0x7F800000; // Filter out NaN's if (Float.isNaN(f)) { return f; } // Zero's are also a special case if (f == 0f) { final float smallestPositiveFloat = Float.intBitsToFloat(1); return (positive) ? smallestPositiveFloat : -smallestPositiveFloat; } // If entering here, d is a nonzero value. // Hold all bits in a int for later use. final int bits = Float.floatToIntBits(f); // Strip off the sign bit. int magnitude = bits & ~SIGN; // If next float away from zero, increase magnitude. // Else decrease magnitude if ((bits > 0) == positive) { if (magnitude != POSITIVEINFINITY) { magnitude++; } } else { magnitude--; } // Restore sign bit and return. final int signbit = bits & SIGN; return Float.intBitsToFloat(magnitude | signbit); } /** * Fill the specified rectangle of <code>raster</code> with the provided background values. Suppose the raster is initialized to 0. Thus, for * binary data, if the provided background values are 0, do nothing. */ public static void fillBackground(WritableRaster raster, Rectangle rect, double[] backgroundValues) { rect = rect.intersection(raster.getBounds()); // int numBands = raster.getSampleModel().getNumBands(); SampleModel sm = raster.getSampleModel(); PixelAccessor accessor = new PixelAccessor(sm, null); if (isBinary(sm)) { // fill binary data byte value = (byte) (((int) backgroundValues[0]) & 1); if (value == 0) return; int rectX = rect.x; int rectY = rect.y; int rectWidth = rect.width; int rectHeight = rect.height; int dx = rectX - raster.getSampleModelTranslateX(); int dy = rectY - raster.getSampleModelTranslateY(); DataBuffer dataBuffer = raster.getDataBuffer(); MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel) sm; int lineStride = mpp.getScanlineStride(); int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy); int bitOffset = mpp.getBitOffset(dx); switch (sm.getDataType()) { case DataBuffer.TYPE_BYTE: { byte[] data = ((DataBufferByte) dataBuffer).getData(); int bits = bitOffset & 7; int otherBits = (bits == 0) ? 0 : 8 - bits; byte mask = (byte) (255 >> bits); int lineLength = (rectWidth - otherBits) / 8; int bits1 = (rectWidth - otherBits) & 7; byte mask1 = (byte) (255 << (8 - bits1)); // If operating within a single byte, merge masks into one // and don't apply second mask after while loop if (lineLength == 0) { mask &= mask1; bits1 = 0; } for (int y = 0; y < rectHeight; y++) { int start = eltOffset; int end = start + lineLength; if (bits != 0) data[start++] |= mask; while (start < end) data[start++] = (byte) 255; if (bits1 != 0) data[start] |= mask1; eltOffset += lineStride; } break; } case DataBuffer.TYPE_USHORT: { short[] data = ((DataBufferUShort) dataBuffer).getData(); int bits = bitOffset & 15; int otherBits = (bits == 0) ? 0 : 16 - bits; short mask = (short) (65535 >> bits); int lineLength = (rectWidth - otherBits) / 16; int bits1 = (rectWidth - otherBits) & 15; short mask1 = (short) (65535 << (16 - bits1)); // If operating within a single byte, merge masks into one // and don't apply second mask after while loop if (lineLength == 0) { mask &= mask1; bits1 = 0; } for (int y = 0; y < rectHeight; y++) { int start = eltOffset; int end = start + lineLength; if (bits != 0) data[start++] |= mask; while (start < end) data[start++] = (short) 0xFFFF; if (bits1 != 0) data[start++] |= mask1; eltOffset += lineStride; } break; } case DataBuffer.TYPE_INT: { int[] data = ((DataBufferInt) dataBuffer).getData(); int bits = bitOffset & 31; int otherBits = (bits == 0) ? 0 : 32 - bits; int mask = 0xFFFFFFFF >> bits; int lineLength = (rectWidth - otherBits) / 32; int bits1 = (rectWidth - otherBits) & 31; int mask1 = 0xFFFFFFFF << (32 - bits1); // If operating within a single byte, merge masks into one // and don't apply second mask after while loop if (lineLength == 0) { mask &= mask1; bits1 = 0; } for (int y = 0; y < rectHeight; y++) { int start = eltOffset; int end = start + lineLength; if (bits != 0) data[start++] |= mask; while (start < end) data[start++] = 0xFFFFFFFF; if (bits1 != 0) data[start++] |= mask1; eltOffset += lineStride; } break; } } } else { int srcSampleType = accessor.sampleType == PixelAccessor.TYPE_BIT ? DataBuffer.TYPE_BYTE : accessor.sampleType; UnpackedImageData uid = accessor.getPixels(raster, rect, srcSampleType, false); rect = uid.rect; int lineStride = uid.lineStride; int pixelStride = uid.pixelStride; switch (uid.type) { case DataBuffer.TYPE_BYTE: byte[][] bdata = uid.getByteData(); for (int b = 0; b < accessor.numBands; b++) { byte value = (byte) backgroundValues[b]; byte[] bd = bdata[b]; int lastLine = uid.bandOffsets[b] + rect.height * lineStride; for (int lo = uid.bandOffsets[b]; lo < lastLine; lo += lineStride) { int lastPixel = lo + rect.width * pixelStride; for (int po = lo; po < lastPixel; po += pixelStride) { bd[po] = value; } } } break; case DataBuffer.TYPE_USHORT: case DataBuffer.TYPE_SHORT: short[][] sdata = uid.getShortData(); for (int b = 0; b < accessor.numBands; b++) { short value = (short) backgroundValues[b]; short[] sd = sdata[b]; int lastLine = uid.bandOffsets[b] + rect.height * lineStride; for (int lo = uid.bandOffsets[b]; lo < lastLine; lo += lineStride) { int lastPixel = lo + rect.width * pixelStride; for (int po = lo; po < lastPixel; po += pixelStride) { sd[po] = value; } } } break; case DataBuffer.TYPE_INT: int[][] idata = uid.getIntData(); for (int b = 0; b < accessor.numBands; b++) { int value = (int) backgroundValues[b]; int[] id = idata[b]; int lastLine = uid.bandOffsets[b] + rect.height * lineStride; for (int lo = uid.bandOffsets[b]; lo < lastLine; lo += lineStride) { int lastPixel = lo + rect.width * pixelStride; for (int po = lo; po < lastPixel; po += pixelStride) { id[po] = value; } } } break; case DataBuffer.TYPE_FLOAT: float[][] fdata = uid.getFloatData(); for (int b = 0; b < accessor.numBands; b++) { float value = (float) backgroundValues[b]; float[] fd = fdata[b]; int lastLine = uid.bandOffsets[b] + rect.height * lineStride; for (int lo = uid.bandOffsets[b]; lo < lastLine; lo += lineStride) { int lastPixel = lo + rect.width * pixelStride; for (int po = lo; po < lastPixel; po += pixelStride) { fd[po] = value; } } } break; case DataBuffer.TYPE_DOUBLE: double[][] ddata = uid.getDoubleData(); for (int b = 0; b < accessor.numBands; b++) { double value = backgroundValues[b]; double[] dd = ddata[b]; int lastLine = uid.bandOffsets[b] + rect.height * lineStride; for (int lo = uid.bandOffsets[b]; lo < lastLine; lo += lineStride) { int lastPixel = lo + rect.width * pixelStride; for (int po = lo; po < lastPixel; po += pixelStride) { dd[po] = value; } } } break; } } } /** * Check whether a <code>SampleModel</code> represents a binary data set, i.e., a single band of data with one bit per pixel packed into a * <code>MultiPixelPackedSampleModel</code>. */ public static boolean isBinary(SampleModel sm) { return sm instanceof MultiPixelPackedSampleModel && ((MultiPixelPackedSampleModel) sm).getPixelBitStride() == 1 && sm.getNumBands() == 1; } }