/* 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.orderdither; import it.geosolutions.jaiext.range.Range; import java.awt.RenderingHints; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.renderable.ParameterBlock; import javax.media.jai.ColorCube; import javax.media.jai.JAI; import javax.media.jai.KernelJAI; import javax.media.jai.OperationDescriptorImpl; import javax.media.jai.ParameterBlockJAI; import javax.media.jai.ROI; import javax.media.jai.RenderedOp; import javax.media.jai.registry.RenderedRegistryMode; /** * An <code>OperationDescriptor</code> describing the "OrderedDither" operation. * * <p> * The "OrderedDither" operation performs color quantization by finding the nearest color to each pixel in a supplied color cube and "shifting" the * resulting index value by a pseudo-random amount determined by the values of a supplied dither mask. * * <p> * The dither mask is supplied as an array of <code>KernelJAI</code> objects the length of which must equal the number of bands in the image. Each * element of the array is a <code>KernelJAI</code> object which represents the dither mask matrix for the corresponding band. All * <code>KernelJAI</code> objects in the array must have the same dimensions and contain floating point values greater than or equal to 0.0 and less * than or equal to 1.0. * * <p> * For all integral data types, the source image samples are presumed to occupy the full range of the respective types. For floating point data types * it is assumed that the data samples have been scaled to the range [0.0, 1.0]. * * <p> * Notice that it is possible to define a {@link ROI} object for reducing the computation area. Also it is possible to define a {@link Range} of * nodata for checking if a Pixel is a NoData one. * * <p> * <table border=1> * <caption>Resource List</caption> * <tr> * <th>Name</th> * <th>Value</th> * </tr> * <tr> * <td>GlobalName</td> * <td>OrderedDither</td> * </tr> * <tr> * <td>LocalName</td> * <td>OrderedDither</td> * </tr> * <tr> * <td>Vendor</td> * <td>it.geosolutions.jaiext</td> * </tr> * <tr> * <td>Description</td> * <td>Performs ordered dither color quantization taking into account ROI and NoData.</td> * </tr> * <tr> * <td>DocURL</td> * <td></td> * </tr> * <tr> * <td>Version</td> * <td>1.0</td> * </tr> * <tr> * <td>arg0Desc</td> * <td>Input color cube.</td> * </tr> * <tr> * <td>arg1Desc</td> * <td>Input dither mask.</td> * </tr> * <tr> * <td>arg2Desc</td> * <td>The ROI to be used for reducing calculation area.</td> * </tr> * <tr> * <td>arg3Desc</td> * <td>The Nodata parameter to check.</td> * </tr> * <tr> * <td>arg4Desc</td> * <td>The destination nodata parameter used to substitute the old nodata one.</td> * </tr> * </table> * </p> * * <p> * <table border=1> * <caption>Parameter List</caption> * <tr> * <th>Name</th> * <th>Class Type</th> * <th>Default Value</th> * </tr> * <tr> * <td>colorMap</td> * <td>javax.media.jai.ColorCube</td> * <td>ColorCube.BYTE_496</td> * <tr> * <td>ditherMask</td> * <td>javax.media.jai.KernelJAI[]</td> * <td>KernelJAI.DITHER_MASK_443</td> * <tr> * <td>roi</td> * <td>javax.media.jai.KernelJAI[]</td> * <td>null</td> * <tr> * <td>nodata</td> * <td>it.geosolutions.jaiext.range</td> * <td>null</td> * <tr> * <td>destNoData</td> * <td>Double</td> * <td>0d</td> * </table> * </p> */ public class OrderedDitherDescriptor extends OperationDescriptorImpl { /** Constructor. */ public OrderedDitherDescriptor() { super(new String[][] { { "GlobalName", "OrderedDither" }, { "LocalName", "OrderedDither" }, { "Vendor", "it.geosolutions.jaiext" }, { "Description", JaiI18N.getString("OrderedDitherDescriptor0") }, { "DocURL", "" }, { "Version", JaiI18N.getString("DescriptorVersion") }, { "arg0Desc", JaiI18N.getString("OrderedDitherDescriptor1") }, { "arg1Desc", JaiI18N.getString("OrderedDitherDescriptor2") }, { "arg2Desc", JaiI18N.getString("OrderedDitherDescriptor3") }, { "arg3Desc", JaiI18N.getString("OrderedDitherDescriptor4") }, { "arg4Desc", JaiI18N.getString("OrderedDitherDescriptor5") } }, new String[] { "rendered" }, 1, new String[] { "colorMap", "ditherMask", "roi", "nodata", "destNoData" }, new Class[] { javax.media.jai.ColorCube.class, javax.media.jai.KernelJAI[].class, javax.media.jai.ROI.class, it.geosolutions.jaiext.range.Range.class, Double.class }, new Object[] { ColorCube.BYTE_496, KernelJAI.DITHER_MASK_443, null, null, 0d }, null); } /** * Method to check the validity of the color map parameter. The supplied color cube must have the same data type and number of bands as the source * image. * * @param sourceImage The source image of the operation. * @param colorMap The color cube. * @param msg The buffer to which messages should be appended. * * @return Whether the color map is valid. */ private static boolean isValidColorMap(RenderedImage sourceImage, ColorCube colorMap, StringBuffer msg) { SampleModel srcSampleModel = sourceImage.getSampleModel(); if (colorMap.getDataType() != srcSampleModel.getTransferType()) { msg.append(JaiI18N.getString("OrderedDitherDescriptor6")); return false; } else if (colorMap.getNumBands() != srcSampleModel.getNumBands()) { msg.append(JaiI18N.getString("OrderedDitherDescriptor7")); return false; } return true; } /** * Method to check the validity of the dither mask parameter. The dither mask is an array of <code>KernelJAI</code> objects wherein the number of * elements in the array must equal the number of bands in the source image. Furthermore all kernels in the array must have the same width and * height. Finally all data elements of all kernels must be greater than or equal to zero and less than or equal to unity. * * @param sourceImage The source image of the operation. * @param ditherMask The dither mask. * @param msg The buffer to which messages should be appended. * * @return Whether the dither mask is valid. */ private static boolean isValidDitherMask(RenderedImage sourceImage, KernelJAI[] ditherMask, StringBuffer msg) { if (ditherMask.length != sourceImage.getSampleModel().getNumBands()) { msg.append(JaiI18N.getString("OrderedDitherDescriptor8")); return false; } int maskWidth = ditherMask[0].getWidth(); int maskHeight = ditherMask[0].getHeight(); for (int band = 0; band < ditherMask.length; band++) { if (ditherMask[band].getWidth() != maskWidth || ditherMask[band].getHeight() != maskHeight) { msg.append(JaiI18N.getString("OrderedDitherDescriptor9")); return false; } float[] kernelData = ditherMask[band].getKernelData(); for (int i = 0; i < kernelData.length; i++) { if (kernelData[i] < 0.0F || kernelData[i] > 1.0) { msg.append(JaiI18N.getString("OrderedDitherDescriptor10")); return false; } } } return true; } /** * Validates the input source and parameters. * * <p> * In addition to the standard checks performed by the superclass method, this method checks that "colorMap" and "ditherMask" are valid for the * given source image. */ public boolean validateArguments(String modeName, ParameterBlock args, StringBuffer msg) { if (!super.validateArguments(modeName, args, msg)) { return false; } if (!modeName.equalsIgnoreCase("rendered")) return true; // Retrieve the operation source and parameters. RenderedImage src = args.getRenderedSource(0); ColorCube colorMap = (ColorCube) args.getObjectParameter(0); KernelJAI[] ditherMask = (KernelJAI[]) args.getObjectParameter(1); // Check color map validity. if (!isValidColorMap(src, colorMap, msg)) { return false; } // Check dither mask validity. if (!isValidDitherMask(src, ditherMask, msg)) { return false; } return true; } /** * Performs ordered dither color quantization using a specified color cube and dither mask. * * <p> * Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints</code> and invokes * {@link JAI#create(String,ParameterBlock,RenderingHints)}. * * @see JAI * @see ParameterBlockJAI * @see RenderedOp * * @param source0 <code>RenderedImage</code> source 0. * @param colorMap The color cube. May be <code>null</code>. * @param ditherMask The dither mask. May be <code>null</code>. * @param hints The <code>RenderingHints</code> to use. May be <code>null</code>. * @param roi Optional ROI object used for reducing computation * @param nodata Optional Range used for checking NoData * @param destNoData Value related to the Output NoData * @return The <code>RenderedOp</code> destination. */ public static RenderedOp create(RenderedImage source0, ColorCube colorMap, KernelJAI[] ditherMask, RenderingHints hints, ROI roi, Range nodata, Double destNoData) { ParameterBlockJAI pb = new ParameterBlockJAI("OrderedDither", RenderedRegistryMode.MODE_NAME); // Setting source pb.setSource("source0", source0); // Setting parameters pb.setParameter("colorMap", colorMap); pb.setParameter("ditherMask", ditherMask); pb.setParameter("roi", roi); pb.setParameter("nodata", nodata); pb.setParameter("destNoData", destNoData); return JAI.create("OrderedDither", pb, hints); } }