/** * Copyright (C) 2009-2014 Cars and Tracks Development Project (CTDP). * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package net.ctdp.rfdynhud.render; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import net.ctdp.rfdynhud.util.NumberUtil; import sun.awt.image.ByteInterleavedRaster; /** * <p> * The {@link ImageTemplate} is a container for image data configured through a property. * </p> * * <p> * You can get a scaled representation of the image or directly draw a scaled version * to another image. * </p> * * @author Marvin Froehlich (CTDP) */ public class ImageTemplate { private final String name; private final BufferedImage bufferedImage; /** * Gets the image template's name. * * @return the image template's name. */ public final String getName() { return ( name ); } /** * Gets the base width of the image. This can be the physical image size * or the size defined in an SVG image. * * @return the base width of the image. */ public final int getBaseWidth() { return ( bufferedImage.getWidth() ); } /** * Gets the base height of the image. This can be the physical image size * or the size defined in an SVG image. * * @return the base height of the image. */ public final int getBaseHeight() { return ( bufferedImage.getHeight() ); } /** * Gets the base aspect ratio of the image. * * @return the base aspect ratio of the image. */ public final float getBaseAspect() { return ( getBaseWidth() / (float)getBaseHeight() ); } /** * Gets, whether the image has an alpha channel or not. * * @return whether the image has an alpha channel or not. */ public final boolean hasAlpha() { return ( bufferedImage.getColorModel().hasAlpha() ); } private void copyPixels( TextureImage2D texture ) { ByteInterleavedRaster raster = (ByteInterleavedRaster)bufferedImage.getRaster(); int[] byteOffsets = raster.getDataOffsets(); byte[] srcBytes = ( (DataBufferByte)bufferedImage.getRaster().getDataBuffer() ).getData(); /* if ( ( ByteOrderManager.RED == byteOffsets[0] ) && ( ByteOrderManager.GREEN == byteOffsets[1] ) && ( ByteOrderManager.BLUE == byteOffsets[2] ) && ( ByteOrderManager.ALPHA == byteOffsets[3] ) ) { texture = TextureImage2D.createOfflineTexture( bufferedImage.getWidth(), bufferedImage.getHeight(), bufferedImage.getColorModel().hasAlpha(), srcBytes ); } else */ { int pixelStride = ( bufferedImage.getColorModel().hasAlpha() ? 4 : 3 ); //byte[] data = new byte[ bufferedImage.getWidth() * bufferedImage.getHeight() * pixelStride ]; byte[] data = texture.getData(); final int w = getBaseWidth(); final int h = getBaseHeight(); int sOffset = 0; int dOffset = 0; for ( int j = 0; j < h; j++ ) { for ( int i = 0; i < w; i++ ) { data[dOffset + ByteOrderManager.RED] = srcBytes[sOffset + byteOffsets[0]]; data[dOffset + ByteOrderManager.GREEN] = srcBytes[sOffset + byteOffsets[1]]; data[dOffset + ByteOrderManager.BLUE] = srcBytes[sOffset + byteOffsets[2]]; if ( pixelStride == 4 ) data[dOffset + ByteOrderManager.ALPHA] = srcBytes[sOffset + byteOffsets[3]]; dOffset += pixelStride; sOffset += pixelStride; } dOffset += pixelStride * ( texture.getMaxWidth() - texture.getWidth() ); } //texture = TextureImage2D.createOfflineTexture( bi.getWidth(), bi.getHeight(), bi.getColorModel().hasAlpha(), data ); } } /** * Draws a scaled representation of this image template to the given texture image. * * @param sx source x * @param sy source y * @param sw source width * @param sh source height * @param dx destination x * @param dy destination y * @param dw destination width * @param dh destination height * @param texture the texture to draw on * @param clearBefore clear before drawing? */ public void drawScaled( int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, TextureImage2D texture, boolean clearBefore ) { if ( ( bufferedImage.getRaster() instanceof ByteInterleavedRaster ) && ( bufferedImage.getColorModel().hasAlpha() == texture.hasAlphaChannel() ) && ( sx == 0 ) && ( sy == 0 ) && ( sw == getBaseWidth() ) && ( sh == getBaseHeight() ) && ( dx == 0 ) && ( dy == 0 ) && ( dw == sw ) && ( dh == sh ) && clearBefore ) { copyPixels( texture ); } else { if ( clearBefore ) texture.clear( dx, dy, dw, dh, false, null ); Texture2DCanvas texCanvas = texture.getTextureCanvas(); Object oldInterpolation = texCanvas.getRenderingHint( RenderingHints.KEY_INTERPOLATION ); texCanvas.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC ); texCanvas.drawImage( bufferedImage, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh ); if ( oldInterpolation == null ) texCanvas.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR ); else texCanvas.setRenderingHint( RenderingHints.KEY_INTERPOLATION, oldInterpolation ); } } /** * Draws a scaled representation of this image template to the given texture image. * * @param x destination x * @param y destination y * @param width destination width * @param height destination height * @param texture the texture to draw on * @param clearBefore clear before drawing? */ public void drawScaled( int x, int y, int width, int height, TextureImage2D texture, boolean clearBefore ) { drawScaled( 0, 0, getBaseWidth(), getBaseHeight(), x, y, width, height, texture, clearBefore ); } /** * Gets a scaled representation of this image template. * * @param width destination width * @param height destination height * @param usePowerOfTwoSize if true, the created texture is created with power of two width and height (with used size set to the desired values). * This is useful in editor mode avoid constant recreations. * * @return a scaled representation of this image template. */ public TextureImage2D getScaledTextureImage( int width, int height, boolean usePowerOfTwoSize ) { int maxWidth = usePowerOfTwoSize ? NumberUtil.roundUpPower2( width ) : width; int maxHeight = usePowerOfTwoSize ? NumberUtil.roundUpPower2( height ) : height; TextureImage2D texture = TextureImage2D.createDrawTexture( maxWidth, maxHeight, width, height, bufferedImage.getColorModel().hasAlpha() ); texture.setName( getName() ); drawScaled( 0, 0, width, height, texture, true ); return ( texture ); } /** * Gets a scaled representation of this image template. * If the possibleResult is non null and has the correct size, it is returned. * * @param width destination width * @param height destination height * @param possibleResult this instance is possibly returned, if parameters match * @param tryToResize if true, the passed in texture is resized to the given size, if the max size is sufficient. * This is useful in editor mode avoid constant recreations. * * @return a scaled representation of this image template. */ public final TextureImage2D getScaledTextureImage( int width, int height, TextureImage2D possibleResult, boolean tryToResize ) { int oldW = -1; int oldH = -1; if ( possibleResult != null ) { oldW = possibleResult.getWidth(); oldH = possibleResult.getHeight(); } TextureImage2D texture = TextureImage2D.getOrCreateDrawTexture( width, height, true, possibleResult, tryToResize ); if ( texture != possibleResult ) texture.setName( getName() ); if ( ( ( oldW > 0 ) || ( oldH > 0 ) ) && ( texture == possibleResult ) ) { texture.clear( 0, 0, Math.max( oldW, width ), Math.max( oldH, height ), false, null ); drawScaled( 0, 0, width, height, texture, false ); } else { drawScaled( 0, 0, width, height, texture, true ); } return ( texture ); } /** * Gets a representation of this image template. If this template encapsulates * a fixed sized image, pixel data is copied directly, otherwise it is drawed at the base size. * * @return a representation of this image template with base size. */ public final TextureImage2D getTextureImage() { return ( getScaledTextureImage( getBaseWidth(), getBaseHeight(), false ) ); } /** * Gets a {@link TransformableTexture} with this image drawn onto it. * * @param width destination width * @param height destination height * @param usePowerOfTwoSize if true, the created texture is created with power of two width and height (with used size set to the desired values). * This is useful in editor mode to avoid constant recreations. * * @return a {@link TransformableTexture} with this image drawn onto it. */ public TransformableTexture getScaledTransformableTexture( int width, int height, boolean usePowerOfTwoSize ) { TransformableTexture texture = new TransformableTexture( width, height, 0, 0, 0, 0, 0f, 1f, 1f, usePowerOfTwoSize ); texture.getTexture().setName( getName() ); drawScaled( 0, 0, width, height, texture.getTexture(), true ); return ( texture ); } /** * Gets a {@link TransformableTexture} with this image drawn onto it. * If the possibleResult is non null and has the correct size, it is returned. * * @param width destination width * @param height destination height * @param possibleResult this instance is possibly returned, if parameters match * @param tryToResize if true, the passed in texture is resized to the given size, if the max size is sufficient. * This is useful in editor mode avoid constant recreations. * @param changeInfo if non <code>null</code> the first element tells you, whether 'possibleResult' has been recycled and the second element, whether the texture has been (re)drawn * * @return a {@link TransformableTexture} with this image drawn onto it. */ public TransformableTexture getScaledTransformableTexture( int width, int height, TransformableTexture possibleResult, boolean tryToResize, boolean[] changeInfo ) { final boolean usePowerOfTwoSizes = tryToResize; int width2 = usePowerOfTwoSizes ? NumberUtil.roundUpPower2( width ) : width; int height2 = usePowerOfTwoSizes ? NumberUtil.roundUpPower2( height ) : height; if ( possibleResult != null ) { if ( usePowerOfTwoSizes ) { if ( ( width2 == possibleResult.getTexture().getMaxWidth() ) && ( height2 == possibleResult.getTexture().getMaxHeight() ) ) { if ( ( width != possibleResult.getTexture().getWidth() ) || ( height != possibleResult.getTexture().getHeight() ) ) { int oldW = possibleResult.getTexture().getWidth(); int oldH = possibleResult.getTexture().getHeight(); possibleResult.getTexture().resize( width, height ); possibleResult.getTexture().clearUpdateList(); possibleResult.getTexture().clear( 0, 0, Math.max( oldW, width ), Math.max( oldH, height ), false, null ); drawScaled( 0, 0, width, height, possibleResult.getTexture(), false ); if ( changeInfo != null ) { if ( changeInfo.length >= 1 ) changeInfo[0] = true; if ( changeInfo.length >= 2 ) changeInfo[1] = true; } } else if ( possibleResult.isDynamic() ) { drawScaled( 0, 0, width, height, possibleResult.getTexture(), true ); if ( changeInfo != null ) { if ( changeInfo.length >= 1 ) changeInfo[0] = true; if ( changeInfo.length >= 2 ) changeInfo[1] = true; } } else if ( changeInfo != null ) { if ( changeInfo.length >= 1 ) changeInfo[0] = true; if ( changeInfo.length >= 2 ) changeInfo[1] = false; } return ( possibleResult ); } } else if ( ( width == possibleResult.getTexture().getWidth() ) && ( height == possibleResult.getTexture().getHeight() ) ) { //possibleResult.getTexture().clearUpdateList(); if ( possibleResult.isDynamic() ) { drawScaled( 0, 0, width, height, possibleResult.getTexture(), true ); if ( changeInfo != null ) { if ( changeInfo.length >= 1 ) changeInfo[0] = true; if ( changeInfo.length >= 2 ) changeInfo[1] = true; } } else if ( changeInfo != null ) { if ( changeInfo.length >= 1 ) changeInfo[0] = true; if ( changeInfo.length >= 2 ) changeInfo[1] = false; } return ( possibleResult ); } } if ( changeInfo != null ) { if ( changeInfo.length >= 1 ) changeInfo[0] = false; if ( changeInfo.length >= 2 ) changeInfo[1] = true; } return ( getScaledTransformableTexture( width, height, usePowerOfTwoSizes ) ); } /** * Gets a {@link TransformableTexture} with this image drawn onto it. * If the possibleResult is non null and has the correct size, it is returned. * * @param width destination width * @param height destination height * @param possibleResult this instance is possibly returned, if parameters match * @param tryToResize if true, the passed in texture is resized to the given size, if the max size is sufficient. * This is useful in editor mode avoid constant recreations. * * @return a {@link TransformableTexture} with this image drawn onto it. */ public TransformableTexture getScaledTransformableTexture( int width, int height, TransformableTexture possibleResult, boolean tryToResize ) { return ( getScaledTransformableTexture( width, height, possibleResult, tryToResize, null ) ); } /** * Gets a {@link TransformableTexture} with this image drawn onto it using base size. * * @return a {@link TransformableTexture} with this image drawn onto it. */ public TransformableTexture getTransformableTexture() { return ( getScaledTransformableTexture( getBaseWidth(), getBaseHeight(), false ) ); } public ImageTemplate( String name, BufferedImage bufferedImage ) { this.name = name; this.bufferedImage = bufferedImage; } }