Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Support for multiple input transparent colors for grid formats #31

Closed
wants to merge 2 commits into
from
Jump to file or symbol
Failed to load files and symbols.
+306 −30
Split
@@ -19,6 +19,7 @@
import java.awt.Color;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.imageio.ImageWriteParam;
@@ -31,6 +32,7 @@
import org.geotools.parameter.DefaultParameterDescriptor;
import org.geotools.referencing.crs.DefaultEngineeringCRS;
import org.geotools.referencing.factory.epsg.CartesianAuthorityFactory;
+import org.geotools.util.Utilities;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridCoverageReader;
import org.opengis.coverage.grid.GridCoverageWriter;
@@ -188,6 +190,10 @@
public static final ParameterDescriptor<Color> INPUT_TRANSPARENT_COLOR = new DefaultParameterDescriptor<Color>(
"InputTransparentColor", Color.class, null, null);
+ /** Control the transparency of the input coverages (allows to set multiple transparent colors). */
+ public static final ParameterDescriptor<Set<Color>> INPUT_TRANSPARENT_COLORS = new DefaultParameterDescriptor<Set<Color>>(
+ "InputTransparentColors", Utilities.<Class<Set<Color>>>cast(Set.class), null, null);
+
/** Control the background color to be used where the input was transparent */
public static final ParameterDescriptor<Color> BACKGROUND_COLOR = new DefaultParameterDescriptor<Color>(
"BackgroundColor", Color.class, null, null);
@@ -29,6 +29,7 @@
import java.awt.image.DataBuffer;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
+import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.PackedColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
@@ -40,9 +41,11 @@
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -1757,6 +1760,45 @@ public final ImageWorker makeColorTransparent(final Color transparentColor)
throw new IllegalStateException(Errors.format(ErrorKeys.UNSUPPORTED_DATA_TYPE));
}
+ /**
+ * Replaces all occurences of the given colors (usually opaque) by a fully transparent color.
+ * Currents implementation supports image backed by any {@link IndexColorModel}, or by
+ * {@link ComponentColorModel} with {@link DataBuffer#TYPE_BYTE TYPE_BYTE}.
+ *
+ * @param transparentColors Colors to make transparent.
+ * @return this image worker.
+ *
+ * @throws IllegalStateException if the current {@linkplain #image} has an unsupported color
+ * model.
+ */
+ public final ImageWorker makeColorsTransparent(Set<Color> transparentColors)
+ throws IllegalStateException {
+ if (transparentColors == null || transparentColors.isEmpty()) {
+ return this;
+ }
+ if (transparentColors.size() == 1) {
+ Color color = transparentColors.iterator().next();
+ return makeColorTransparent(color);
+ }
+
+ if (image.getSampleModel() instanceof MultiPixelPackedSampleModel) {
+ forceComponentColorModel();
+ }
+
+ ColorModel cm = image.getColorModel();
+ if (cm instanceof IndexColorModel) {
+ return maskIndexColorModel(transparentColors);
+ }
+ if (cm instanceof ComponentColorModel) {
+ switch (image.getSampleModel().getDataType()) {
+ case DataBuffer.TYPE_BYTE: {
+ return maskComponentColorModelByte(transparentColors);
+ }
+ }
+ }
+ throw new IllegalStateException(Errors.format(ErrorKeys.UNSUPPORTED_DATA_TYPE));
+ }
+
/**
* For an image backed by an {@link IndexColorModel}, replaces all occurences of the given
* color (usually opaque) by a fully transparent color.
@@ -1765,7 +1807,7 @@ public final ImageWorker makeColorTransparent(final Color transparentColor)
* @return this image worker.
*
*/
- private final ImageWorker maskIndexColorModel(final Color transparentColor) {
+ private final ImageWorker maskIndexColorModel(final Set<Color> transparentColors) {
assert image.getColorModel() instanceof IndexColorModel;
// Gets informations about the provided images.
@@ -1774,13 +1816,13 @@ private final ImageWorker maskIndexColorModel(final Color transparentColor) {
int transparency = cm.getTransparency();
int transparencyIndex = cm.getTransparentPixel();
final int mapSize = cm.getMapSize();
- final int transparentRGB = transparentColor.getRGB() & 0x00FFFFFF;
/*
* Optimization in case of Transparency.BITMASK.
* If the color we want to use as the fully transparent one is the same
* that is actually used as the transparent color, we leave doing nothing.
*/
- if (transparency == Transparency.BITMASK && transparencyIndex != -1) {
+ if (transparency == Transparency.BITMASK && transparencyIndex != -1 && transparentColors.size() == 1) {
+ int transparentRGB = transparentColors.iterator().next().getRGB() & 0x00FFFFFF;
int transpColor = cm.getRGB(transparencyIndex) & 0x00FFFFFF;
if (transpColor == transparentRGB) {
return this;
@@ -1795,11 +1837,8 @@ private final ImageWorker maskIndexColorModel(final Color transparentColor) {
for (int i=0; i<mapSize; i++) {
// Gets the color for this pixel removing the alpha information.
final int color = cm.getRGB(i) & 0xFFFFFF;
- if (transparentRGB == color) {
+ if (transparentColors.contains(new Color(color))) {
transparentPixelsIndexes.add(i);
- if (Transparency.BITMASK == transparency) {
- break;
- }
}
}
final int found = transparentPixelsIndexes.size();
@@ -1847,6 +1886,10 @@ private final ImageWorker maskIndexColorModel(final Color transparentColor) {
return this;
}
+ private final ImageWorker maskIndexColorModel(final Color transparentColor) {
+ return maskIndexColorModel(Collections.singleton(transparentColor));
+ }
+
/**
* For an image backed by an {@link ComponentColorModel}, replaces all occurences
* of the given color (usually opaque) by a fully transparent color.
@@ -1865,7 +1908,7 @@ private final ImageWorker maskIndexColorModel(final Color transparentColor) {
* depends on statistics on pixel values) and avoid unwanted side-effect like turning black
* color (RGB = 0,0,0) to transparent one. It would also be easier to maintain I believe.
*/
- private final ImageWorker maskComponentColorModelByte(final Color transparentColor) {
+ private final ImageWorker maskComponentColorModelByte(final Set<Color> transparentColors) {
assert image.getColorModel() instanceof ComponentColorModel;
assert image.getSampleModel().getDataType() == DataBuffer.TYPE_BYTE;
/*
@@ -1920,21 +1963,29 @@ private final ImageWorker maskComponentColorModelByte(final Color transparentCol
if (singleStep) {
final byte[] data = tableData[0];
Arrays.fill(data, (byte) 255);
- data[transparentColor.getRed()] = 0;
+ for (Color color : transparentColors) {
+ data[color.getRed()] = 0;
+ }
} else {
switch (numColorBands) {
case 3:
Arrays.fill(tableData[2], (byte) 255);
- tableData[2][transparentColor.getBlue() ] = 0; // fall through
-
- case 2:
+ for (Color color : transparentColors) {
+ tableData[2][color.getBlue()] = 0;
+ }
+ // fall through
+ case 2:
Arrays.fill(tableData[1], (byte) 255);
- tableData[1][transparentColor.getGreen()] = 0; // fall through
-
- case 1:
- Arrays.fill(tableData[0], (byte) 255);
- tableData[0][transparentColor.getRed() ] = 0; // fall through
-
+ for (Color color : transparentColors) {
+ tableData[1][color.getGreen()] = 0;
+ }
+ // fall through
+ case 1:
+ Arrays.fill(tableData[0], (byte) 255);
+ for (Color color : transparentColors) {
+ tableData[0][color.getRed()] = 0;
+ }
+ // fall through
case 0: break;
}
}
@@ -1970,6 +2021,10 @@ private final ImageWorker maskComponentColorModelByte(final Color transparentCol
return this;
}
+ private ImageWorker maskComponentColorModelByte(final Color transparentColor) {
+ return maskComponentColorModelByte(Collections.singleton(transparentColor));
+ }
+
/**
* Inverts the pixel values of the {@linkplain #image}.
*
@@ -43,6 +43,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import java.util.Vector;
import javax.imageio.ImageIO;
@@ -909,6 +910,28 @@ public static RenderedImage maskColor(final Color transparentColor,
return w.makeColorTransparent(transparentColor).getRenderedImage();
}
+ /**
+ * Relies on the {@link ImageWorker} to mask a certain colors from an image.
+ *
+ * @param transparentColors a set of {@link Color}s to make transparent
+ * @param image the {@link RenderedImage} to work on
+ * @return a new {@link RenderedImage} where the provided {@link Color}s have turned into transparent.
+ *
+ * @throws IllegalStateException
+ */
+ public static RenderedImage maskColors(final Set<Color> transparentColors,
+ final RenderedImage image) throws IllegalStateException {
+ Utilities.ensureNonNull("image", image);
+ if(transparentColors==null || transparentColors.isEmpty()){
+ return image;
+ }
+ final ImageWorker w = new ImageWorker(image);
+ if (image.getSampleModel() instanceof MultiPixelPackedSampleModel){
+ w.forceComponentColorModel();
+ }
+ return w.makeColorsTransparent(transparentColors).getRenderedImage();
+ }
+
static public ImageReadParam cloneImageReadParam(ImageReadParam param) {
// The ImageReadParam passed in is non-null. As the
@@ -0,0 +1,145 @@
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2004-2010, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+
+package org.geotools.image;
+
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+import java.awt.image.IndexColorModel;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * @author Sebastian Graca, ISPiK S.A.
+ */
+@RunWith(Parameterized.class)
+public class ImageWorkerMultiColorTransparencyTest {
+ public ImageWorkerMultiColorTransparencyTest(BufferedImage image) {
+ this.image = image;
+ }
+
+ @Parameterized.Parameters
+ public static List<BufferedImage[]> createImages() {
+ return Arrays.asList(
+ new BufferedImage[]{makeIndexedImage()},
+ new BufferedImage[]{makeComponentImage()}
+ );
+ }
+
+ @Test
+ public void noTransparency() throws Exception {
+ Color[] colors = {
+ new Color(0xaa, 0xbb, 0xcc),
+ new Color(0x11, 0x22, 0x33)
+ };
+ Color[][] expected = IMAGE_DATA;
+ checkTransparency(colors, expected);
+ }
+
+ @Test
+ public void singleColor() throws Exception {
+ Color[] colors = {
+ new Color(0xaa, 0xbb, 0xcc),
+ new Color(0x11, 0x22, 0x33),
+ new Color(0xb1, 0xb2, 0xb3)
+ };
+ Color[][] expected = new Color[][]{
+ {new Color(0xa1, 0xa2, 0xa3), new Color(0xb1, 0xb2, 0xb3, 0x00), new Color(0xc1, 0xc2, 0xc3)},
+ {new Color(0xd1, 0xd2, 0xd3), new Color(0xe1, 0xe2, 0xe3), new Color(0xf1, 0xf2, 0xf3)}
+ };
+ checkTransparency(colors, expected);
+ }
+
+ @Test
+ public void multipleColors() throws Exception {
+ Color[] colors = {
+ new Color(0xaa, 0xbb, 0xcc),
+ new Color(0x11, 0x22, 0x33),
+ new Color(0xb1, 0xb2, 0xb3),
+ new Color(0xd1, 0xd2, 0xd3)
+ };
+ Color[][] expected = new Color[][]{
+ {new Color(0xa1, 0xa2, 0xa3), new Color(0xb1, 0xb2, 0xb3, 0x00), new Color(0xc1, 0xc2, 0xc3)},
+ {new Color(0xd1, 0xd2, 0xd3, 0x00), new Color(0xe1, 0xe2, 0xe3), new Color(0xf1, 0xf2, 0xf3)}
+ };
+ checkTransparency(colors, expected);
+ }
+
+ private void checkTransparency(Color[] transparentColors, Color[][] expected) {
+ ImageWorker w = new ImageWorker(makeIndexedImage());
+ w.makeColorsTransparent(new HashSet<Color>(Arrays.asList(transparentColors)));
+ RenderedImage actual = w.getRenderedImage();
+ Raster raster = actual.getData();
+
+ for (int y = 0; y < image.getHeight(); ++y) {
+ for (int x = 0; x < image.getWidth(); ++x) {
+ int expectedRgb = expected[y][x].getRGB();
+ int actualRgb = actual.getColorModel().getRGB(raster.getDataElements(x, y, null));
+ assertEquals("x=" + x + " y=" + y, expectedRgb, actualRgb);
+ }
+ }
+ }
+
+ private static BufferedImage makeComponentImage() {
+ BufferedImage image = new BufferedImage(IMAGE_DATA[0].length, IMAGE_DATA.length, BufferedImage.TYPE_INT_ARGB);
+ for (int y = 0; y < IMAGE_DATA.length; ++y) {
+ for (int x = 0; x < IMAGE_DATA[y].length; ++x) {
+ image.setRGB(x, y, IMAGE_DATA[y][x].getRGB());
+ }
+ }
+ return image;
+ }
+
+ private static BufferedImage makeIndexedImage() {
+ int width = IMAGE_DATA[0].length;
+ byte[][] rgb = new byte[3][width * IMAGE_DATA.length];
+ for (int y = 0; y < IMAGE_DATA.length; ++y) {
+ for (int x = 0; x < IMAGE_DATA[y].length; ++x) {
+ rgb[0][y * width + x] = (byte)IMAGE_DATA[y][x].getRed();
+ rgb[1][y * width + x] = (byte)IMAGE_DATA[y][x].getGreen();
+ rgb[2][y * width + x] = (byte)IMAGE_DATA[y][x].getBlue();
+ }
+ }
+
+ IndexColorModel colorModel = new IndexColorModel(8, width * IMAGE_DATA.length, rgb[0], rgb[1], rgb[2]);
+ BufferedImage image = new BufferedImage(IMAGE_DATA[0].length, IMAGE_DATA.length, BufferedImage.TYPE_BYTE_INDEXED, colorModel);
+ for (int y = 0; y < IMAGE_DATA.length; ++y) {
+ for (int x = 0; x < IMAGE_DATA[y].length; ++x) {
+ image.setRGB(x, y, IMAGE_DATA[y][x].getRGB());
+ }
+ }
+ return image;
+ }
+
+ private final BufferedImage image;
+
+ private static final Color[][] IMAGE_DATA = new Color[][]{
+ {new Color(0xa1, 0xa2, 0xa3), new Color(0xb1, 0xb2, 0xb3), new Color(0xc1, 0xc2, 0xc3)},
+ {new Color(0xd1, 0xd2, 0xd3), new Color(0xe1, 0xe2, 0xe3), new Color(0xf1, 0xf2, 0xf3)}
+ };
+}
Oops, something went wrong.