package com.jme3.scene.plugins.blender.textures.blending; import com.jme3.math.FastMath; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.textures.TexturePixel; import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; /** * <p> * The class that is responsible for blending the following texture types: * </p> * <ul> * <li>Luminance8</li> * <li>Luminance8Alpha8</li> * </ul> * <p> * Not yet supported (but will be): * </p> * <ul> * <li>Luminance16</li> * <li>Luminance16Alpha16</li> * <li>Luminance16F</li> * <li>Luminance16FAlpha16F</li> * <li>Luminance32F</li> * </ul> * * @author Marcin Roguski (Kaelthas) */ public class TextureBlenderLuminance extends AbstractTextureBlender { private static final Logger LOGGER = Logger.getLogger(TextureBlenderLuminance.class.getName()); public TextureBlenderLuminance(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { super(flag, negateTexture, blendType, materialColor, color, blendFactor); } public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { this.prepareImagesForBlending(image, baseImage); Format format = image.getFormat(); PixelInputOutput basePixelIO = null; TexturePixel basePixel = null; float[] materialColor = this.materialColor; if (baseImage != null) { basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); materialColor = new float[this.materialColor.length]; basePixel = new TexturePixel(); } int width = image.getWidth(); int height = image.getHeight(); int depth = image.getDepth(); if (depth == 0) { depth = 1; } ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth); float[] resultPixel = new float[4]; float[] tinAndAlpha = new float[2]; for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { ByteBuffer data = image.getData(dataLayerIndex); data.rewind(); ByteBuffer newData = BufferUtils.createByteBuffer(data.limit() * 4); int dataIndex = 0, x = 0, y = 0; while (data.hasRemaining()) { // getting the proper material color if the base texture is applied if (basePixelIO != null) { basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y); basePixel.toRGBA(materialColor); ++x; if (x >= width) { x = 0; ++y; } } this.getTinAndAlpha(data, format, negateTexture, tinAndAlpha); this.blendPixel(resultPixel, materialColor, color, tinAndAlpha[0], blendFactor, blendType, blenderContext); newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); newData.put(dataIndex++, (byte) (tinAndAlpha[1] * 255.0f)); } dataArray.add(newData); } Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray, ColorSpace.Linear) : new Image(Format.RGBA8, width, height, dataArray.get(0), ColorSpace.Linear); if (image.getMipMapSizes() != null) { result.setMipMapSizes(image.getMipMapSizes().clone()); } return result; } /** * This method return texture intensity and alpha value. * * @param data * the texture data * @param imageFormat * the image format * @param neg * indicates if the texture is negated * @param result * the table (2 elements) where the result is being stored */ protected void getTinAndAlpha(ByteBuffer data, Format imageFormat, boolean neg, float[] result) { byte pixelValue = data.get();// at least one byte is always taken float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; switch (imageFormat) { case Luminance8: result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; result[1] = 1.0f; break; case Luminance8Alpha8: result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; pixelValue = data.get(); result[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; break; case Luminance16F: case Luminance16FAlpha16F: case Luminance32F: LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat); break; default: throw new IllegalStateException("Invalid image format type for Luminance texture blender: " + imageFormat); } } /** * This method blends the texture with an appropriate color. * * @param result * the result color (variable 'in' in blender source code) * @param materialColor * the texture color (variable 'out' in blender source coude) * @param color * the previous color (variable 'tex' in blender source code) * @param textureIntensity * texture intensity (variable 'fact' in blender source code) * @param textureFactor * texture affection factor (variable 'facg' in blender source * code) * @param blendtype * the blend type * @param blenderContext * the blender context */ protected void blendPixel(float[] result, float[] materialColor, float[] color, float textureIntensity, float textureFactor, int blendtype, BlenderContext blenderContext) { float oneMinusFactor, col; textureIntensity *= textureFactor; switch (blendtype) { case MTEX_BLEND: oneMinusFactor = 1.0f - textureIntensity; result[0] = textureIntensity * color[0] + oneMinusFactor * materialColor[0]; result[1] = textureIntensity * color[1] + oneMinusFactor * materialColor[1]; result[2] = textureIntensity * color[2] + oneMinusFactor * materialColor[2]; break; case MTEX_MUL: oneMinusFactor = 1.0f - textureFactor; result[0] = (oneMinusFactor + textureIntensity * materialColor[0]) * color[0]; result[1] = (oneMinusFactor + textureIntensity * materialColor[1]) * color[1]; result[2] = (oneMinusFactor + textureIntensity * materialColor[2]) * color[2]; break; case MTEX_DIV: oneMinusFactor = 1.0f - textureIntensity; if (color[0] != 0.0) { result[0] = (oneMinusFactor * materialColor[0] + textureIntensity * materialColor[0] / color[0]) * 0.5f; } if (color[1] != 0.0) { result[1] = (oneMinusFactor * materialColor[1] + textureIntensity * materialColor[1] / color[1]) * 0.5f; } if (color[2] != 0.0) { result[2] = (oneMinusFactor * materialColor[2] + textureIntensity * materialColor[2] / color[2]) * 0.5f; } break; case MTEX_SCREEN: oneMinusFactor = 1.0f - textureFactor; result[0] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); result[1] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); result[2] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); break; case MTEX_OVERLAY: oneMinusFactor = 1.0f - textureFactor; if (materialColor[0] < 0.5f) { result[0] = color[0] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[0]); } else { result[0] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); } if (materialColor[1] < 0.5f) { result[1] = color[1] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[1]); } else { result[1] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); } if (materialColor[2] < 0.5f) { result[2] = color[2] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[2]); } else { result[2] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); } break; case MTEX_SUB: result[0] = materialColor[0] - textureIntensity * color[0]; result[1] = materialColor[1] - textureIntensity * color[1]; result[2] = materialColor[2] - textureIntensity * color[2]; result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); break; case MTEX_ADD: result[0] = (textureIntensity * color[0] + materialColor[0]) * 0.5f; result[1] = (textureIntensity * color[1] + materialColor[1]) * 0.5f; result[2] = (textureIntensity * color[2] + materialColor[2]) * 0.5f; break; case MTEX_DIFF: oneMinusFactor = 1.0f - textureIntensity; result[0] = oneMinusFactor * materialColor[0] + textureIntensity * Math.abs(materialColor[0] - color[0]); result[1] = oneMinusFactor * materialColor[1] + textureIntensity * Math.abs(materialColor[1] - color[1]); result[2] = oneMinusFactor * materialColor[2] + textureIntensity * Math.abs(materialColor[2] - color[2]); break; case MTEX_DARK: col = textureIntensity * color[0]; result[0] = col < materialColor[0] ? col : materialColor[0]; col = textureIntensity * color[1]; result[1] = col < materialColor[1] ? col : materialColor[1]; col = textureIntensity * color[2]; result[2] = col < materialColor[2] ? col : materialColor[2]; break; case MTEX_LIGHT: col = textureIntensity * color[0]; result[0] = col > materialColor[0] ? col : materialColor[0]; col = textureIntensity * color[1]; result[1] = col > materialColor[1] ? col : materialColor[1]; col = textureIntensity * color[2]; result[2] = col > materialColor[2] ? col : materialColor[2]; break; case MTEX_BLEND_HUE: case MTEX_BLEND_SAT: case MTEX_BLEND_VAL: case MTEX_BLEND_COLOR: System.arraycopy(materialColor, 0, result, 0, 3); this.blendHSV(blendtype, result, textureIntensity, color, blenderContext); break; default: throw new IllegalStateException("Unknown blend type: " + blendtype); } } }