From 41cad759d23fea83178b568c2b482db9afcee024 Mon Sep 17 00:00:00 2001 From: tpylak <tpylak> Date: Tue, 11 Jan 2011 15:59:51 +0000 Subject: [PATCH] LMS-1965 support black color as transparent in non-transparent overlay images, recognize image transparency automatically SVN: 19367 --- .../dss/generic/shared/utils/ImageUtil.java | 24 ++-- .../dss/etl/Hdf5ThumbnailGenerator.java | 2 +- .../server/images/ImageChannelsUtils.java | 123 +++++++++++++++--- 3 files changed, 118 insertions(+), 31 deletions(-) diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java index 26209b5feff..52521107b74 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java @@ -160,14 +160,19 @@ public class ImageUtil */ public static boolean isImageFile(File file) { - String name = file.getName(); + String fileName = file.getName(); + String fileType = tryGetFileExtension(fileName); + return fileType != null && FILE_TYPES.contains(fileType); + } + + private static String tryGetFileExtension(String name) + { int lastIndexOfDot = name.lastIndexOf('.'); if (lastIndexOfDot < 0) { - return false; + return null; } - String fileType = name.substring(lastIndexOfDot + 1).toLowerCase(); - return FILE_TYPES.contains(fileType); + return name.substring(lastIndexOfDot + 1).toLowerCase(); } /** @@ -267,7 +272,7 @@ public class ImageUtil */ public static BufferedImage createThumbnail(BufferedImage image, int maxWidth, int maxHeight) { - return rescale(image, maxWidth, maxHeight, true, false); + return rescale(image, maxWidth, maxHeight, true); } /** @@ -279,11 +284,9 @@ public class ImageUtil * @param maxHeight Maximum height of the result image. * @param enlargeIfNecessary if false and the image has smaller width and height than the * specified limit, then the image is not changed. - * @param preserveAlpha if true alpha channel will be not ignored (and image will take more - * space) */ public static BufferedImage rescale(BufferedImage image, int maxWidth, int maxHeight, - boolean enlargeIfNecessary, boolean preserveAlpha) + boolean enlargeIfNecessary) { int width = image.getWidth(); int height = image.getHeight(); @@ -304,7 +307,10 @@ public class ImageUtil int thumbnailWidth = (int) (scale * width + 0.5); int thumbnailHeight = (int) (scale * height + 0.5); - int imageType = preserveAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB; + // preserve alpha channel if it was present before + int imageType = + image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB + : BufferedImage.TYPE_INT_RGB; BufferedImage thumbnail = new BufferedImage(thumbnailWidth, thumbnailHeight, imageType); Graphics2D graphics2D = thumbnail.createGraphics(); graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java index 1997be2239d..1fd91d69d57 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java @@ -72,7 +72,7 @@ class Hdf5ThumbnailGenerator implements IHdf5WriterClient File img = new File(imagesInStoreFolder, imagePath); BufferedImage image = ImageUtil.loadImage(img); BufferedImage thumbnail = - ImageUtil.rescale(image, thumbnailMaxWidth, thumbnailMaxHeight, false, true); + ImageUtil.rescale(image, thumbnailMaxWidth, thumbnailMaxHeight, false); ByteArrayOutputStream output = new ByteArrayOutputStream(); try { diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java index b16f7d65062..4e4ab5dc897 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java @@ -115,24 +115,24 @@ public class ImageChannelsUtils { // NOTE: never merges the overlays, draws each channel separately (merging looses // transparency and is slower) - List<BufferedImage> overlayImages = + List<ImageWithReference> overlayImages = getSingleImagesSkipNonExisting(overlayChannels, overlaySize, datasetDirectoryProvider); - for (BufferedImage overlayImage : overlayImages) + for (ImageWithReference overlayImage : overlayImages) { if (image != null) { drawOverlay(image, overlayImage); } else { - image = overlayImage; + image = overlayImage.getBufferedImage(); } } } return createResponseContentStream(image, null); } - private static List<BufferedImage> getSingleImagesSkipNonExisting( + private static List<ImageWithReference> getSingleImagesSkipNonExisting( DatasetAcquiredImagesReference imagesReference, RequestedImageSize imageSize, IDatasetDirectoryProvider datasetDirectoryProvider) { @@ -445,7 +445,7 @@ public class ImageChannelsUtils start = operationLog.isDebugEnabled() ? System.currentTimeMillis() : 0; image = ImageUtil.rescale(image, size.getWidth(), size.getHeight(), - requestedSize.enlargeIfNecessary(), true); + requestedSize.enlargeIfNecessary()); if (operationLog.isDebugEnabled()) { operationLog.debug("Create thumbnail: " + (System.currentTimeMillis() - start)); @@ -492,8 +492,8 @@ public class ImageChannelsUtils return calculateAndTransformSingleImage(imageReference, transform, allChannelsMerged); } else { - List<BufferedImage> images = calculateSingleImages(imageReferences); - BufferedImage mergedImage = mergeImages(images, imageReferences); + List<ImageWithReference> images = calculateSingleImages(imageReferences); + BufferedImage mergedImage = mergeImages(images); // NOTE: even if we are not merging all the channels but just few of them we use the // merged-channel transformation IImageTransformerFactory transformerFactory = @@ -513,13 +513,37 @@ public class ImageChannelsUtils return factoryOrNull.createTransformer().transform(input); } - private static List<BufferedImage> calculateSingleImages( + private static class ImageWithReference + { + private final BufferedImage image; + + private final AbsoluteImageReference reference; + + public ImageWithReference(BufferedImage image, AbsoluteImageReference reference) + { + this.image = image; + this.reference = reference; + } + + public BufferedImage getBufferedImage() + { + return image; + } + + public AbsoluteImageReference getReference() + { + return reference; + } + } + + private static List<ImageWithReference> calculateSingleImages( List<AbsoluteImageReference> imageReferences) { - List<BufferedImage> images = new ArrayList<BufferedImage>(); + List<ImageWithReference> images = new ArrayList<ImageWithReference>(); for (AbsoluteImageReference imageRef : imageReferences) { - images.add(calculateSingleImage(imageRef)); + BufferedImage image = calculateSingleImage(imageRef); + images.add(new ImageWithReference(image, imageRef)); } return images; } @@ -559,13 +583,11 @@ public class ImageChannelsUtils return (i1OrNull == null) ? (i2OrNull == null) : i1OrNull.equals(i2OrNull); } - private static BufferedImage mergeImages(List<BufferedImage> images, - List<AbsoluteImageReference> imageReferences) + private static BufferedImage mergeImages(List<ImageWithReference> images) { assert images.size() > 1 : "more than 1 image expected, but found: " + images.size(); - assert images.size() == imageReferences.size() : "images.size() != imageReferences.size()"; - BufferedImage newImage = createNewImage(images.get(0)); + BufferedImage newImage = createNewImage(images.get(0).getBufferedImage()); int width = newImage.getWidth(); int height = newImage.getHeight(); int colorBuffer[] = new int[4]; @@ -573,30 +595,89 @@ public class ImageChannelsUtils { for (int y = 0; y < height; y++) { - int mergedRGB = mergeRGBColor(images, imageReferences, x, y, colorBuffer); + int mergedRGB = mergeRGBColor(images, x, y, colorBuffer); newImage.setRGB(x, y, mergedRGB); } } return newImage; } - private static void drawOverlay(BufferedImage image, BufferedImage overlayImage) + private static void drawOverlay(BufferedImage image, ImageWithReference overlayImage) + { + BufferedImage overlayBufferedImage = overlayImage.getBufferedImage(); + if (supportsTransparency(overlayImage)) + { + drawTransparentOverlayFast(image, overlayBufferedImage); + } else + { + drawOverlaySlow(image, overlayBufferedImage); + } + } + + private static void drawTransparentOverlayFast(BufferedImage image, + BufferedImage overlayBufferedImage) { Graphics2D graphics = image.createGraphics(); AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER); graphics.setComposite(ac); - graphics.drawImage(overlayImage, null, null); + graphics.drawImage(overlayBufferedImage, null, null); + } + + /** + * Draws overlay by computing the maximal color components (r,g,b) for each pixel. In this way + * both transparent and black pixels are not drawn. Useful when overlays are saved in a format + * which does not support transparency. + */ + private static void drawOverlaySlow(BufferedImage image, BufferedImage overlayImage) + { + int width = Math.min(image.getWidth(), overlayImage.getWidth()); + int height = Math.min(image.getHeight(), overlayImage.getHeight()); + + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + int imageRGB = image.getRGB(x, y); + int overlayRGB = overlayImage.getRGB(x, y); + int overlayedRGB = overlayRGBColor(imageRGB, overlayRGB); + image.setRGB(x, y, overlayedRGB); + } + } + } + + // creates a color with a maximum value of each component + private static int overlayRGBColor(int imageRGB, int overlayRGB) + { + Color imageColor = new Color(imageRGB); + Color overlayColor = new Color(overlayRGB, true); + + if (overlayColor.getAlpha() == 0) + { + return imageRGB; // overlay is transparent, return the original pixel + } else + { + int r = Math.max(imageColor.getRed(), overlayColor.getRed()); + int g = Math.max(imageColor.getGreen(), overlayColor.getGreen()); + int b = Math.max(imageColor.getBlue(), overlayColor.getBlue()); + return new Color(r, g, b).getRGB(); + } + } + + private static boolean supportsTransparency(ImageWithReference image) + { + return image.getBufferedImage().getColorModel().hasAlpha(); } - private static int mergeRGBColor(List<BufferedImage> images, - List<AbsoluteImageReference> imageReferences, int x, int y, int colorBuffer[]) + private static int mergeRGBColor(List<ImageWithReference> images, int x, int y, + int colorBuffer[]) { Arrays.fill(colorBuffer, 0); for (int index = 0; index < images.size(); index++) { - int rgb = images.get(index).getRGB(x, y); + ImageWithReference image = images.get(index); + int rgb = image.getBufferedImage().getRGB(x, y); Color singleColor = new Color(rgb, true); - int channelIndex = imageReferences.get(index).getChannelIndex(); + int channelIndex = image.getReference().getChannelIndex(); for (int i : getRGBColorIndexes(channelIndex)) { colorBuffer[i] = Math.max(colorBuffer[i], extractMaxColorIngredient(singleColor)); -- GitLab