package mil.nga.giat.geowave.adapter.vector.render; import java.awt.AlphaComposite; import java.awt.Graphics2D; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.tuple.Pair; import org.geoserver.wms.map.ImageUtils; import mil.nga.giat.geowave.core.index.Mergeable; public class DistributedRenderResult implements Mergeable { public static class CompositeGroupResult implements Mergeable { private PersistableComposite composite; // keep each style separate so they can be composited together in the // original draw order private List<Pair<PersistableRenderedImage, PersistableComposite>> orderedStyles; protected CompositeGroupResult() {} public CompositeGroupResult( final PersistableComposite composite, final List<Pair<PersistableRenderedImage, PersistableComposite>> orderedStyles ) { this.composite = composite; this.orderedStyles = orderedStyles; } private void render( final Graphics2D parentGraphics, final int width, final int height ) { Graphics2D graphics; BufferedImage compositeGroupImage = null; if ((composite != null) && (composite.getComposite() != null)) { // this will render to a back buffer so that compositeGroupImage = parentGraphics.getDeviceConfiguration().createCompatibleImage( width, height, Transparency.TRANSLUCENT); graphics = compositeGroupImage.createGraphics(); graphics.setRenderingHints(parentGraphics.getRenderingHints()); } else { graphics = parentGraphics; } for (final Pair<PersistableRenderedImage, PersistableComposite> currentStyle : orderedStyles) { if ((currentStyle == null) || (currentStyle.getKey() == null) || (currentStyle.getKey().image == null)) { continue; } if ((currentStyle.getValue() == null) || (currentStyle.getValue().getComposite() == null)) { graphics.setComposite(AlphaComposite.SrcOver); } else { graphics.setComposite(currentStyle.getValue().getComposite()); } graphics.drawImage( currentStyle.getKey().image, 0, 0, null); } if (compositeGroupImage != null) { if ((composite == null) || (composite.getComposite() == null)) { parentGraphics.setComposite(AlphaComposite.SrcOver); } else { parentGraphics.setComposite(composite.getComposite()); } parentGraphics.drawImage( compositeGroupImage, 0, 0, null); graphics.dispose(); } } @Override public byte[] toBinary() { final byte[] compositeBinary; if (composite != null) { compositeBinary = composite.toBinary(); } else { compositeBinary = new byte[] {}; } final List<byte[]> styleBinaries = new ArrayList<>( orderedStyles.size()); int bufferSize = compositeBinary.length + 8; for (final Pair<PersistableRenderedImage, PersistableComposite> style : orderedStyles) { byte[] styleBinary; if (style != null) { byte[] styleCompositeBinary; if (style.getRight() != null) { styleCompositeBinary = style.getRight().toBinary(); } else { styleCompositeBinary = new byte[] {}; } byte[] styleImageBinary; if (style.getLeft() != null) { styleImageBinary = style.getLeft().toBinary(); } else { styleImageBinary = new byte[] {}; } final ByteBuffer styleBuf = ByteBuffer.allocate(styleCompositeBinary.length + styleImageBinary.length + 4); styleBuf.putInt(styleCompositeBinary.length); if (styleCompositeBinary.length > 0) { styleBuf.put(styleCompositeBinary); } if (styleImageBinary.length > 0) { styleBuf.put(styleImageBinary); } styleBinary = styleBuf.array(); } else { styleBinary = new byte[] {}; } styleBinaries.add(styleBinary); bufferSize += (styleBinary.length + 4); } final ByteBuffer buf = ByteBuffer.allocate(bufferSize); buf.putInt(compositeBinary.length); if (compositeBinary.length > 0) { buf.put(compositeBinary); } buf.putInt(styleBinaries.size()); for (final byte[] styleBinary : styleBinaries) { buf.putInt(styleBinary.length); buf.put(styleBinary); } return buf.array(); } @Override public void fromBinary( final byte[] bytes ) { final ByteBuffer buf = ByteBuffer.wrap(bytes); final byte[] compositeBinary = new byte[buf.getInt()]; if (compositeBinary.length > 0) { buf.get(compositeBinary); composite = new PersistableComposite(); composite.fromBinary(compositeBinary); } else { composite = null; } final int styleLength = buf.getInt(); orderedStyles = new ArrayList<>( styleLength); for (int i = 0; i < styleLength; i++) { final int styleBinaryLength = buf.getInt(); if (styleBinaryLength > 0) { final byte[] styleBinary = new byte[styleBinaryLength]; buf.get(styleBinary); final ByteBuffer styleBuf = ByteBuffer.wrap(styleBinary); final int styleCompositeBinaryLength = styleBuf.getInt(); PersistableComposite styleComposite; if (styleCompositeBinaryLength > 0) { final byte[] styleCompositeBinary = new byte[styleCompositeBinaryLength]; styleBuf.get(styleCompositeBinary); styleComposite = new PersistableComposite(); styleComposite.fromBinary(styleCompositeBinary); } else { styleComposite = null; } final int styleImageBinaryLength = styleBinary.length - styleCompositeBinaryLength - 4; PersistableRenderedImage styleImage; if (styleImageBinaryLength > 0) { final byte[] styleImageBinary = new byte[styleImageBinaryLength]; styleBuf.get(styleImageBinary); styleImage = new PersistableRenderedImage(); styleImage.fromBinary(styleImageBinary); } else { styleImage = null; } orderedStyles.add(Pair.of( styleImage, styleComposite)); } else { orderedStyles.add(null); } } } @Override public void merge( final Mergeable merge ) { if (merge instanceof CompositeGroupResult) { final CompositeGroupResult other = (CompositeGroupResult) merge; final List<Pair<PersistableRenderedImage, PersistableComposite>> newOrderedStyles = new ArrayList<>(); final int minStyles = Math.min( orderedStyles.size(), other.orderedStyles.size()); for (int i = 0; i < minStyles; i++) { final Pair<PersistableRenderedImage, PersistableComposite> thisStyle = orderedStyles.get(i); final Pair<PersistableRenderedImage, PersistableComposite> otherStyle = other.orderedStyles.get(i); // all composites should be the same, if they're not then // these composite groups got mis-ordered by style // keep in mind that they can be null if nothing was // rendered to this style or other style because of rules // applied to that specific subset of data not resulting in // anything rendered for the style if (thisStyle != null) { if (otherStyle != null) { // render the images together and just arbitrarily // grab "this" composite as they both should be the // same newOrderedStyles.add(Pair.of( mergeImage( thisStyle.getLeft(), otherStyle.getLeft()), thisStyle.getRight())); } else { newOrderedStyles.add(thisStyle); } } else { newOrderedStyles.add(otherStyle); } } if (orderedStyles.size() > minStyles) { // hopefully this is never the case, but just in case newOrderedStyles.addAll(orderedStyles.subList( minStyles, orderedStyles.size())); } if (other.orderedStyles.size() > minStyles) { // hopefully this is never the case, but just in case newOrderedStyles.addAll(other.orderedStyles.subList( minStyles, other.orderedStyles.size())); } orderedStyles = newOrderedStyles; } } } // geotools has a concept of composites, which we need to keep separate so // that they can be composited in the original draw order, by default there // is only a single composite private List<CompositeGroupResult> orderedComposites; // the parent image essentially gets labels rendered to it private PersistableRenderedImage parentImage; protected DistributedRenderResult() {} public DistributedRenderResult( final PersistableRenderedImage parentImage, final List<CompositeGroupResult> orderedComposites ) { this.parentImage = parentImage; this.orderedComposites = orderedComposites; } public BufferedImage renderComposite( final DistributedRenderOptions renderOptions ) { final BufferedImage image = ImageUtils.createImage( renderOptions.getMapWidth(), renderOptions.getMapHeight(), renderOptions.getPalette(), renderOptions.isTransparent() || renderOptions.isMetatile()); final Graphics2D graphics = ImageUtils.prepareTransparency( renderOptions.isTransparent(), renderOptions.getBgColor(), image, null); for (final CompositeGroupResult compositeGroup : orderedComposites) { compositeGroup.render( graphics, renderOptions.getMapWidth(), renderOptions.getMapHeight()); } final BufferedImage img = parentImage.getImage(); graphics.drawImage( img, 0, 0, null); graphics.dispose(); return image; } @Override public byte[] toBinary() { // 4 bytes for the length as an int, and 4 bytes for the size of // parentImage final byte[] parentImageBinary = parentImage.toBinary(); int byteSize = 8 + parentImageBinary.length; final List<byte[]> compositeBinaries = new ArrayList<>(); for (final CompositeGroupResult compositeGroup : orderedComposites) { final byte[] compositeGroupBinary = compositeGroup.toBinary(); byteSize += (compositeGroupBinary.length + 4); compositeBinaries.add(compositeGroupBinary); } final ByteBuffer buf = ByteBuffer.allocate(byteSize); buf.putInt(parentImageBinary.length); buf.put(parentImageBinary); buf.putInt(orderedComposites.size()); for (final byte[] compositeGroupBinary : compositeBinaries) { buf.putInt(compositeGroupBinary.length); buf.put(compositeGroupBinary); } return buf.array(); } @Override public void fromBinary( final byte[] bytes ) { final ByteBuffer buf = ByteBuffer.wrap(bytes); final byte[] parentImageBinary = new byte[buf.getInt()]; buf.get(parentImageBinary); parentImage = new PersistableRenderedImage(); parentImage.fromBinary(parentImageBinary); final int numCompositeGroups = buf.getInt(); orderedComposites = new ArrayList<>( numCompositeGroups); for (int i = 0; i < numCompositeGroups; i++) { final byte[] compositeGroupBinary = new byte[buf.getInt()]; buf.get(compositeGroupBinary); final CompositeGroupResult compositeGroup = new CompositeGroupResult(); compositeGroup.fromBinary(compositeGroupBinary); orderedComposites.add(compositeGroup); } } @Override public void merge( final Mergeable merge ) { if (merge instanceof DistributedRenderResult) { final DistributedRenderResult other = ((DistributedRenderResult) merge); final int minComposites = Math.min( orderedComposites.size(), other.orderedComposites.size()); // first render parents together if ((parentImage != null) && (parentImage.image != null)) { if ((other.parentImage != null) && (other.parentImage.image != null)) { // all composites should be the same, if they're not // then these distributed results got mis-ordered by // composite group, so composite remains this.composite parentImage = mergeImage( parentImage, other.parentImage); } } else { parentImage = other.parentImage; } final List<CompositeGroupResult> newOrderedComposites = new ArrayList<>(); for (int c = 0; c < minComposites; c++) { final CompositeGroupResult thisCompositeGroup = orderedComposites.get(c); final CompositeGroupResult otherCompositeGroup = other.orderedComposites.get(c); thisCompositeGroup.merge(otherCompositeGroup); newOrderedComposites.add(thisCompositeGroup); } if (orderedComposites.size() > minComposites) { // hopefully this is never the case, but just in case newOrderedComposites.addAll(orderedComposites.subList( minComposites, orderedComposites.size())); } if (other.orderedComposites.size() > minComposites) { // hopefully this is never the case, but just in case newOrderedComposites.addAll(other.orderedComposites.subList( minComposites, other.orderedComposites.size())); } orderedComposites = newOrderedComposites; } } private static PersistableRenderedImage mergeImage( final PersistableRenderedImage image1, final PersistableRenderedImage image2 ) { final Graphics2D graphics = image1.image.createGraphics(); graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); graphics.drawImage( image2.image, 0, 0, null); graphics.dispose(); return new PersistableRenderedImage( image1.image); } }