/******************************************************************************* * Copyright 2012 Geoscience Australia * * 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 au.gov.ga.earthsci.worldwind.common.layers.screenoverlay; import gov.nasa.worldwind.avlist.AVKey; import gov.nasa.worldwind.avlist.AVList; import gov.nasa.worldwind.exception.WWRuntimeException; import gov.nasa.worldwind.geom.Vec4; import gov.nasa.worldwind.layers.AbstractLayer; import gov.nasa.worldwind.render.DrawContext; import gov.nasa.worldwind.render.FrameFactory; import gov.nasa.worldwind.render.OrderedRenderable; import gov.nasa.worldwind.util.Logging; import java.awt.Point; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.net.URL; import java.nio.DoubleBuffer; import javax.imageio.ImageIO; import javax.media.opengl.GL2; import javax.media.opengl.GLProfile; import javax.swing.SwingUtilities; import au.gov.ga.earthsci.worldwind.common.util.Validate; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureCoords; import com.jogamp.opengl.util.texture.awt.AWTTextureIO; /** * A layer that can display html formatted text and images overlayed on the * screen. * * @author James Navin (james.navin@ga.gov.au) */ public class ScreenOverlayLayer extends AbstractLayer { private ScreenOverlayAttributes attributes; private ScreenOverlay overlay = new ScreenOverlay(); private BufferedImage image = null; private boolean imageLoading = false; /** * Create a new {@link ScreenOverlayLayer} with the given source data and * the default attribute values */ public ScreenOverlayLayer(URL sourceUrl) { Validate.notNull(sourceUrl, "Source data URL is required"); this.attributes = new MutableScreenOverlayAttributesImpl(sourceUrl); } /** * Create a new {@link ScreenOverlayLayer} with the given overlay attributes */ public ScreenOverlayLayer(ScreenOverlayAttributes attributes) { Validate.notNull(attributes, "Overlay attributes are required"); this.attributes = attributes; } /** * Create a new {@link ScreenOverlayLayer} with parameters provided in the * given {@link AVList} */ public ScreenOverlayLayer(AVList params) { Validate.notNull(params, "Initialisation parameters are required"); this.attributes = new MutableScreenOverlayAttributesImpl(params); super.setValues(params); } /** * The ordered renderable with eye distance 0 that will render the layer on * top of most other layers */ protected class ScreenOverlay implements OrderedRenderable { @Override public double getDistanceFromEye() { return 0; } @Override public void render(DrawContext dc) { ScreenOverlayLayer.this.draw(dc); } @Override public void pick(DrawContext dc, Point pickPoint) { ScreenOverlayLayer.this.draw(dc); } } public void setAttributes(ScreenOverlayAttributes attributes) { Validate.notNull(attributes, "Attributes are required"); this.attributes = attributes; } public ScreenOverlayAttributes getAttributes() { return this.attributes; } @Override protected void doRender(DrawContext dc) { dc.addOrderedRenderable(this.overlay); } @Override protected void doPick(DrawContext dc, Point pickPoint) { dc.addOrderedRenderable(this.overlay); } protected void draw(DrawContext dc) { if (attributes == null) { return; } GL2 gl = dc.getGL().getGL2(); boolean attribsPushed = false; boolean modelviewPushed = false; boolean projectionPushed = false; try { gl.glPushAttrib(GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_COLOR_BUFFER_BIT | GL2.GL_ENABLE_BIT | GL2.GL_TEXTURE_BIT | GL2.GL_TRANSFORM_BIT | GL2.GL_VIEWPORT_BIT | GL2.GL_CURRENT_BIT | GL2.GL_LINE_BIT); attribsPushed = true; gl.glDisable(GL2.GL_DEPTH_TEST); Rectangle viewport = dc.getView().getViewport(); Rectangle overlay = new Rectangle((int) attributes.getWidth(viewport.width), (int) attributes.getHeight(viewport.height)); // Parallel projection gl.glMatrixMode(GL2.GL_PROJECTION); gl.glPushMatrix(); projectionPushed = true; gl.glLoadIdentity(); double maxwh = Math.max(1, Math.max(overlay.width, overlay.height)); gl.glOrtho(0d, viewport.width, 0d, viewport.height, -0.6 * maxwh, 0.6 * maxwh); // Translate to the correct position gl.glMatrixMode(GL2.GL_MODELVIEW); gl.glPushMatrix(); modelviewPushed = true; gl.glLoadIdentity(); Vec4 location = computeLocation(viewport, overlay); gl.glTranslated(location.x, location.y, location.z); if (!dc.isPickingMode()) { if (attributes.isDrawBorder()) { drawBorder(dc, overlay); } drawOverlay(dc, overlay); } } finally { if (projectionPushed) { gl.glMatrixMode(GL2.GL_PROJECTION); gl.glPopMatrix(); } if (modelviewPushed) { gl.glMatrixMode(GL2.GL_MODELVIEW); gl.glPopMatrix(); } if (attribsPushed) { gl.glPopAttrib(); } } } private void drawBorder(DrawContext dc, Rectangle overlay) { DoubleBuffer buffer = null; buffer = FrameFactory.createShapeBuffer(AVKey.SHAPE_RECTANGLE, (overlay.width + attributes.getBorderWidth() * 2), (overlay.height + attributes.getBorderWidth() * 2), 0, buffer); GL2 gl = dc.getGL().getGL2(); gl.glEnable(GL2.GL_LINE_SMOOTH); gl.glHint(GL2.GL_LINE_SMOOTH_HINT, GL2.GL_NICEST); gl.glLineWidth(attributes.getBorderWidth()); gl.glEnable(GL2.GL_BLEND); float[] compArray = new float[4]; attributes.getBorderColor().getRGBComponents(compArray); compArray[3] = (float) this.getOpacity(); gl.glColor4fv(compArray, 0); gl.glEnable(GL2.GL_BLEND); gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA); gl.glTranslated(-attributes.getBorderWidth() / 2, -attributes.getBorderWidth() / 2, 0); FrameFactory.drawBuffer(dc, GL2.GL_LINE_STRIP, buffer.remaining() / 2, buffer); gl.glTranslated(attributes.getBorderWidth(), attributes.getBorderWidth(), 0); } private void drawOverlay(DrawContext dc, Rectangle overlay) { Texture overlayTexture = getTexture(dc, overlay); if (overlayTexture != null) { GL2 gl = dc.getGL().getGL2(); gl.glEnable(GL2.GL_TEXTURE_2D); overlayTexture.bind(gl); gl.glColor4d(1d, 1d, 1d, this.getOpacity()); gl.glEnable(GL2.GL_BLEND); gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA); TextureCoords texCoords = overlayTexture.getImageTexCoords(); gl.glScaled(overlay.width, overlay.height, 1d); dc.drawUnitQuad(texCoords); } } private Texture getTexture(DrawContext dc, final Rectangle overlay) { Texture texture = dc.getGpuResourceCache().getTexture(attributes.getSourceId()); if (!textureNeedsReloading(texture, overlay)) { return texture; } if (image == null) { if (!imageLoading) { imageLoading = true; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { if (attributes.isSourceHtml()) { if (attributes.getSourceUrl() != null) { image = HtmlToImage.createImageFromHtml(attributes.getSourceUrl(), overlay.width, overlay.height); } else { image = HtmlToImage.createImageFromHtml(attributes.getSourceHtml(), overlay.width, overlay.height); } } else { image = ImageIO.read(attributes.getSourceUrl()); } imageLoading = false; } catch (Exception e) { String msg = Logging.getMessage("layers.IOExceptionDuringInitialization"); Logging.logger().severe(msg); throw new WWRuntimeException(msg, e); } } }); } return null; } texture = AWTTextureIO.newTexture(GLProfile.get(GLProfile.GL2), image, false); dc.getTextureCache().put(attributes.getSourceId(), texture); GL2 gl = dc.getGL().getGL2(); gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE); gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR); gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR); gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE); int[] maxAnisotropy = new int[1]; gl.glGetIntegerv(GL2.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy, 0); gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy[0]); return texture; } private boolean textureNeedsReloading(Texture texture, Rectangle overlay) { if (texture == null) { return true; } if (attributes.isSourceImage()) { // Images will simply be rescaled - don't need to reload for a dimension change return false; } return texture.getImageHeight() != overlay.height || texture.getImageWidth() != overlay.width; } private Vec4 computeLocation(Rectangle viewport, Rectangle overlay) { double x = 0d; double y = 0d; switch (attributes.getPosition()) { case CENTER: { x = ((viewport.width - overlay.width) / 2) - attributes.getBorderWidth(); y = ((viewport.height - overlay.height) / 2) - attributes.getBorderWidth(); break; } case NORTH: { x = ((viewport.width - overlay.width) / 2) - attributes.getBorderWidth(); y = viewport.height - overlay.height - attributes.getBorderWidth(); break; } case NORTHEAST: { x = viewport.width - overlay.width - attributes.getBorderWidth(); y = viewport.height - overlay.height - attributes.getBorderWidth(); break; } case EAST: { x = viewport.width - overlay.width - attributes.getBorderWidth(); y = ((viewport.height - overlay.height) / 2) - attributes.getBorderWidth(); break; } case SOUTHEAST: { x = viewport.width - overlay.width - attributes.getBorderWidth(); y = attributes.getBorderWidth(); break; } case SOUTH: { x = ((viewport.width - overlay.width) / 2) - attributes.getBorderWidth(); y = attributes.getBorderWidth(); break; } case SOUTHWEST: { x = attributes.getBorderWidth(); y = attributes.getBorderWidth(); break; } case WEST: { x = attributes.getBorderWidth(); y = ((viewport.height - overlay.height) / 2) - attributes.getBorderWidth(); break; } case NORTHWEST: { x = attributes.getBorderWidth(); y = viewport.height - overlay.height - attributes.getBorderWidth(); break; } } return new Vec4(x, y, 0); } }