/*
* Copyright (c) 2016, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Metron, Inc. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL METRON, INC. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.metsci.glimpse.dnc;
import static com.metsci.glimpse.dnc.util.DncMiscUtils.nextPowerOfTwo;
import static java.lang.Math.ceil;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.sqrt;
import static java.util.Collections.unmodifiableMap;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.jogamp.opengl.util.packrect.Rect;
import com.jogamp.opengl.util.packrect.RectVisitor;
import com.jogamp.opengl.util.packrect.RectanglePacker;
import com.metsci.glimpse.dnc.util.AnchoredImage;
import com.metsci.glimpse.dnc.util.NullBackingStoreManager;
import com.metsci.glimpse.dnc.util.TexturableImage;
public class DncAtlases
{
public static final int imagePadding = 1;
public static <K> DncHostAtlas<K> createHostAtlas( final Map<K,AnchoredImage> anchoredImages, int maxTextureDim ) throws IOException
{
double totalArea = 0;
for ( AnchoredImage anchoredImage : anchoredImages.values( ) )
{
int wPadded = anchoredImage.image.getWidth( ) + 2*imagePadding;
int hPadded = anchoredImage.image.getHeight( ) + 2*imagePadding;
totalArea += ( wPadded * hPadded );
}
// Initial dims must be at least 2, or RectanglePacker will hang
int initialWidth = max( 2, min( maxTextureDim, nextPowerOfTwo( ( int ) ceil( sqrt( totalArea ) ) ) ) );
int initialHeight = max( 2, ( int ) ceil( totalArea / initialWidth ) );
RectanglePacker rectPacker = new RectanglePacker( new NullBackingStoreManager( ), initialWidth, initialHeight );
rectPacker.setMaxSize( maxTextureDim, maxTextureDim );
for ( Entry<K,AnchoredImage> en : anchoredImages.entrySet( ) )
{
K key = en.getKey( );
AnchoredImage anchoredImage = en.getValue( );
int wPadded = anchoredImage.image.getWidth( ) + 2*imagePadding;
int hPadded = anchoredImage.image.getHeight( ) + 2*imagePadding;
rectPacker.add( new Rect( 0, 0, wPadded, hPadded, key ) );
}
final Map<K,DncAtlasEntry> atlasEntries = new HashMap<>( );
Dimension totalSize = ( Dimension ) rectPacker.getBackingStore( );
final int wTotal = nextPowerOfTwo( totalSize.width );
final int hTotal = nextPowerOfTwo( totalSize.height );
TexturableImage atlasImage = new TexturableImage( wTotal, hTotal );
final Graphics2D g = atlasImage.createGraphics( );
rectPacker.visit( new RectVisitor( )
{
public void visit( Rect rect )
{
@SuppressWarnings( "unchecked" )
K key = ( K ) rect.getUserData( );
AnchoredImage anchoredImage = anchoredImages.get( key );
int xPadded = rect.x( );
int yPadded = rect.y( );
int wPadded = rect.w( );
int hPadded = rect.h( );
int x = xPadded + imagePadding;
int y = yPadded + imagePadding;
int w = wPadded - 2*imagePadding;
int h = hPadded - 2*imagePadding;
g.drawImage( anchoredImage.image, x, y, w, h, null );
float sMin = ( xPadded ) / ( float ) wTotal;
float sMax = ( xPadded + wPadded ) / ( float ) wTotal;
float tMin = ( yPadded ) / ( float ) hTotal;
float tMax = ( yPadded + hPadded ) / ( float ) hTotal;
// XXX: Off by half a pixel?
float xAlign = ( anchoredImage.iAnchor + imagePadding ) / ( float ) wPadded;
float yAlign = ( anchoredImage.jAnchor + imagePadding ) / ( float ) hPadded;
atlasEntries.put( key, new DncAtlasEntry( wPadded, hPadded, sMin, sMax, tMin, tMax, xAlign, yAlign ) );
}
} );
g.dispose( );
return new DncHostAtlas<K>( atlasEntries, atlasImage );
}
public static class DncHostAtlas<K>
{
public final Map<K,DncAtlasEntry> entries;
public final TexturableImage textureImage;
public DncHostAtlas( Map<K,DncAtlasEntry> entries, TexturableImage textureImage )
{
this.entries = unmodifiableMap( entries );
this.textureImage = textureImage;
}
}
public static class DncAtlasEntry
{
// Subimage size, in pixels
public final int w;
public final int h;
// Subimage bounds, as a fraction of atlas-texture dimensions
public final float sMin;
public final float sMax;
public final float tMin;
public final float tMax;
// Alignment: 0 = bottom/left, 1 = top/right
public final float xAlign;
public final float yAlign;
public DncAtlasEntry( int w, int h, float sMin, float sMax, float tMin, float tMax, float xAlign, float yAlign )
{
this.w = w;
this.h = h;
this.sMin = sMin;
this.sMax = sMax;
this.tMin = tMin;
this.tMax = tMax;
this.xAlign = xAlign;
this.yAlign = yAlign;
}
}
}