Support for multilayered textures and textures with mipmaps.
git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9402 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
This commit is contained in:
parent
0090d4eaad
commit
9e2f6b88ef
@ -100,8 +100,7 @@ public class CombinedTexture {
|
|||||||
Texture previousTexture = null;
|
Texture previousTexture = null;
|
||||||
UVCoordinatesType masterUVCoordinatesType = null;
|
UVCoordinatesType masterUVCoordinatesType = null;
|
||||||
for (TextureData textureData : textureDatas) {
|
for (TextureData textureData : textureDatas) {
|
||||||
// decompress compressed textures (all will be merged into one
|
// decompress compressed textures (all will be merged into one texture anyway)
|
||||||
// texture anyway)
|
|
||||||
if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) {
|
if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) {
|
||||||
textureData.texture.setImage(textureHelper.decompress(textureData.texture.getImage()));
|
textureData.texture.setImage(textureHelper.decompress(textureData.texture.getImage()));
|
||||||
textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender);
|
textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender);
|
||||||
@ -228,19 +227,25 @@ public class CombinedTexture {
|
|||||||
* the source texture
|
* the source texture
|
||||||
*/
|
*/
|
||||||
private void merge(Texture2D target, Texture2D source) {
|
private void merge(Texture2D target, Texture2D source) {
|
||||||
|
if(target.getImage().getDepth() != source.getImage().getDepth()) {
|
||||||
|
throw new IllegalArgumentException("Cannot merge images with different depths!");
|
||||||
|
}
|
||||||
Image sourceImage = source.getImage();
|
Image sourceImage = source.getImage();
|
||||||
Image targetImage = target.getImage();
|
Image targetImage = target.getImage();
|
||||||
PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
|
PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
|
||||||
PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
|
PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
|
||||||
TexturePixel sourcePixel = new TexturePixel();
|
TexturePixel sourcePixel = new TexturePixel();
|
||||||
TexturePixel targetPixel = new TexturePixel();
|
TexturePixel targetPixel = new TexturePixel();
|
||||||
|
int depth = target.getImage().getDepth() == 0 ? 1 : target.getImage().getDepth();
|
||||||
|
|
||||||
|
for (int layerIndex = 0; layerIndex < depth; ++layerIndex) {
|
||||||
for (int x = 0; x < sourceImage.getWidth(); ++x) {
|
for (int x = 0; x < sourceImage.getWidth(); ++x) {
|
||||||
for (int y = 0; y < sourceImage.getHeight(); ++y) {
|
for (int y = 0; y < sourceImage.getHeight(); ++y) {
|
||||||
sourceIO.read(sourceImage, sourcePixel, x, y);
|
sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y);
|
||||||
targetIO.read(targetImage, targetPixel, x, y);
|
targetIO.read(targetImage, layerIndex, targetPixel, x, y);
|
||||||
targetPixel.merge(sourcePixel);
|
targetPixel.merge(sourcePixel);
|
||||||
targetIO.write(targetImage, targetPixel, x, y);
|
targetIO.write(targetImage, layerIndex, targetPixel, x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,18 +304,20 @@ public class CombinedTexture {
|
|||||||
case RGBA16:
|
case RGBA16:
|
||||||
case RGBA16F:
|
case RGBA16F:
|
||||||
case RGBA32F:
|
case RGBA32F:
|
||||||
case RGBA8:// with these types it is better to make sure if
|
case RGBA8:// with these types it is better to make sure if the texture is or is not transparent
|
||||||
// the texture is or is not transparent
|
|
||||||
PixelInputOutput pixelInputOutput = PixelIOFactory.getPixelIO(image.getFormat());
|
PixelInputOutput pixelInputOutput = PixelIOFactory.getPixelIO(image.getFormat());
|
||||||
TexturePixel pixel = new TexturePixel();
|
TexturePixel pixel = new TexturePixel();
|
||||||
|
int depth = image.getDepth() == 0 ? 1 : image.getDepth();
|
||||||
|
for (int layerIndex = 0; layerIndex < depth; ++layerIndex) {
|
||||||
for (int x = 0; x < image.getWidth(); ++x) {
|
for (int x = 0; x < image.getWidth(); ++x) {
|
||||||
for (int y = 0; y < image.getHeight(); ++y) {
|
for (int y = 0; y < image.getHeight(); ++y) {
|
||||||
pixelInputOutput.read(image, pixel, x, y);
|
pixelInputOutput.read(image, layerIndex, pixel, x, y);
|
||||||
if (pixel.alpha < 1.0f) {
|
if (pixel.alpha < 1.0f) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.jme3.scene.plugins.blender.textures;
|
package com.jme3.scene.plugins.blender.textures;
|
||||||
|
|
||||||
import com.jme3.math.FastMath;
|
import com.jme3.math.FastMath;
|
||||||
|
import com.jme3.texture.Image.Format;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data that helps in bytes calculations for the result image.
|
* The data that helps in bytes calculations for the result image.
|
||||||
@ -21,36 +22,33 @@ import com.jme3.math.FastMath;
|
|||||||
/** The counter of texel y row. */
|
/** The counter of texel y row. */
|
||||||
private int yCounter;
|
private int yCounter;
|
||||||
/** The width of the image in pixels. */
|
/** The width of the image in pixels. */
|
||||||
private int pixelWidth;
|
private int widthInPixels;
|
||||||
/** The height of the image in pixels. */
|
/** The height of the image in pixels. */
|
||||||
private int pixelHeight;
|
private int heightInPixels;
|
||||||
/** The total texel count. */
|
/** The total texel count. */
|
||||||
private int xTexelCount;
|
private int xTexelCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Allocates the required memory. Initializes variables.
|
* Constructor. Allocates memory for data structures.
|
||||||
*
|
*
|
||||||
* @param textelsCount
|
* @param compressedSize
|
||||||
* the total count of the texels
|
* the size of compressed image (or its mipmap)
|
||||||
* @param pixelWidth
|
* @param widthToHeightRatio
|
||||||
* the width of the image in pixels
|
* width/height ratio for the image
|
||||||
* @param pixelHeight
|
* @param format
|
||||||
* the height of the image in pixels
|
* the format of the image
|
||||||
* @param isAlpha
|
|
||||||
* indicates if the memory for alpha values should be
|
|
||||||
* allocated
|
|
||||||
*/
|
*/
|
||||||
public DDSTexelData(int textelsCount, int pixelWidth, int pixelHeight, boolean isAlpha) {
|
public DDSTexelData(int compressedSize, float widthToHeightRatio, Format format) {
|
||||||
textelsCount = pixelWidth * pixelHeight >> 4;
|
int texelsCount = compressedSize * 8 / format.getBitsPerPixel() / 16;
|
||||||
this.colors = new TexturePixel[textelsCount][];
|
this.colors = new TexturePixel[texelsCount][];
|
||||||
this.indexes = new long[textelsCount];
|
this.indexes = new long[texelsCount];
|
||||||
this.xTexelCount = pixelWidth >> 2;
|
this.widthInPixels = (int) (0.5f * (float) Math.sqrt(this.getSizeInBytes() / widthToHeightRatio));
|
||||||
this.yCounter = (pixelHeight >> 2) - 1;// xCounter is 0 for now
|
this.heightInPixels = (int) (this.widthInPixels / widthToHeightRatio);
|
||||||
this.pixelHeight = pixelHeight;
|
this.xTexelCount = widthInPixels >> 2;
|
||||||
this.pixelWidth = pixelWidth;
|
this.yCounter = (heightInPixels >> 2) - 1;// xCounter is 0 for now
|
||||||
if (isAlpha) {
|
if (format == Format.DXT3 || format == Format.DXT5) {
|
||||||
this.alphas = new float[textelsCount][];
|
this.alphas = new float[texelsCount][];
|
||||||
this.alphaIndexes = new long[textelsCount];
|
this.alphaIndexes = new long[texelsCount];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,12 +102,15 @@ import com.jme3.math.FastMath;
|
|||||||
* the y coordinate of the pixel
|
* the y coordinate of the pixel
|
||||||
* @param result
|
* @param result
|
||||||
* the table where the result is stored
|
* the table where the result is stored
|
||||||
|
* @return <b>true</b> if the pixel was correctly read and <b>false</b> if
|
||||||
|
* the position was outside the image sizes
|
||||||
*/
|
*/
|
||||||
public void getRGBA8(int x, int y, byte[] result) {
|
public boolean getRGBA8(int x, int y, byte[] result) {
|
||||||
int xTexetlIndex = x % pixelWidth / 4;
|
int xTexetlIndex = x % widthInPixels / 4;
|
||||||
int yTexelIndex = y % pixelHeight / 4;
|
int yTexelIndex = y % heightInPixels / 4;
|
||||||
|
|
||||||
int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex;
|
int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex;
|
||||||
|
if (texelIndex < colors.length) {
|
||||||
TexturePixel[] colors = this.colors[texelIndex];
|
TexturePixel[] colors = this.colors[texelIndex];
|
||||||
|
|
||||||
// coordinates of the pixel in the selected texel
|
// coordinates of the pixel in the selected texel
|
||||||
@ -127,5 +128,30 @@ import com.jme3.math.FastMath;
|
|||||||
result[1] = (byte) (colors[colorIndex].green * 255.0f);
|
result[1] = (byte) (colors[colorIndex].green * 255.0f);
|
||||||
result[2] = (byte) (colors[colorIndex].blue * 255.0f);
|
result[2] = (byte) (colors[colorIndex].blue * 255.0f);
|
||||||
result[3] = (byte) (alpha * 255.0f);
|
result[3] = (byte) (alpha * 255.0f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the size of the decompressed texel (in bytes)
|
||||||
|
*/
|
||||||
|
public int getSizeInBytes() {
|
||||||
|
// indexes.length == count of texels
|
||||||
|
return indexes.length * 16 * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return image (mipmap) width
|
||||||
|
*/
|
||||||
|
public int getPixelWidth() {
|
||||||
|
return widthInPixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return image (mipmap) height
|
||||||
|
*/
|
||||||
|
public int getPixelHeight() {
|
||||||
|
return heightInPixels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,19 +242,28 @@ public class TextureHelper extends AbstractBlenderHelper {
|
|||||||
depth = 1;
|
depth = 1;
|
||||||
}
|
}
|
||||||
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
|
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
|
||||||
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
|
int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1];
|
||||||
|
int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null;
|
||||||
|
|
||||||
for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
|
for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
|
||||||
ByteBuffer data = image.getData(dataLayerIndex);
|
ByteBuffer data = image.getData(dataLayerIndex);
|
||||||
data.rewind();
|
data.rewind();
|
||||||
|
if(sizes.length == 1) {
|
||||||
|
sizes[0] = data.remaining();
|
||||||
|
}
|
||||||
|
float widthToHeightRatio = image.getWidth() / image.getHeight();//this should always be constant for each mipmap
|
||||||
|
List<DDSTexelData> texelDataList = new ArrayList<DDSTexelData>(sizes.length);
|
||||||
|
int maxPosition = 0, resultSize = 0;
|
||||||
|
|
||||||
byte[] bytes = new byte[image.getWidth() * image.getHeight() << 2];
|
for(int sizeIndex=0;sizeIndex<sizes.length;++sizeIndex) {
|
||||||
DDSTexelData texelData = new DDSTexelData(data.remaining() * 8/format.getBitsPerPixel()/16/*data.remaining() / (format.getBitsPerPixel() << 1)*/,
|
maxPosition += sizes[sizeIndex];
|
||||||
image.getWidth(), image.getHeight(), format != Format.DXT1);
|
DDSTexelData texelData = new DDSTexelData(sizes[sizeIndex], widthToHeightRatio, format);
|
||||||
|
texelDataList.add(texelData);
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case DXT1:// BC1
|
case DXT1:// BC1
|
||||||
case DXT1A:
|
case DXT1A:
|
||||||
while (data.hasRemaining()) {
|
while (data.position() < maxPosition) {
|
||||||
|
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
|
||||||
short c0 = data.getShort();
|
short c0 = data.getShort();
|
||||||
short c1 = data.getShort();
|
short c1 = data.getShort();
|
||||||
int col0 = RGB565.RGB565_to_ARGB8(c0);
|
int col0 = RGB565.RGB565_to_ARGB8(c0);
|
||||||
@ -287,7 +296,8 @@ public class TextureHelper extends AbstractBlenderHelper {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DXT3:// BC2
|
case DXT3:// BC2
|
||||||
while (data.hasRemaining()) {
|
while (data.position() < maxPosition) {
|
||||||
|
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
|
||||||
long alpha = data.getLong();
|
long alpha = data.getLong();
|
||||||
float[] alphas = new float[16];
|
float[] alphas = new float[16];
|
||||||
long alphasIndex = 0;
|
long alphasIndex = 0;
|
||||||
@ -322,7 +332,8 @@ public class TextureHelper extends AbstractBlenderHelper {
|
|||||||
break;
|
break;
|
||||||
case DXT5:// BC3
|
case DXT5:// BC3
|
||||||
float[] alphas = new float[8];
|
float[] alphas = new float[8];
|
||||||
while (data.hasRemaining()) {
|
while (data.position() < maxPosition) {
|
||||||
|
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
|
||||||
alphas[0] = data.get() * 255.0f;
|
alphas[0] = data.get() * 255.0f;
|
||||||
alphas[1] = data.get() * 255.0f;
|
alphas[1] = data.get() * 255.0f;
|
||||||
long alphaIndices = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40;
|
long alphaIndices = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40;
|
||||||
@ -368,24 +379,34 @@ public class TextureHelper extends AbstractBlenderHelper {
|
|||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Unknown compressed format: " + format);
|
throw new IllegalStateException("Unknown compressed format: " + format);
|
||||||
}
|
}
|
||||||
|
newMipmapSizes[sizeIndex] = texelData.getSizeInBytes();
|
||||||
byte[] pixelBytes = new byte[4];
|
resultSize += texelData.getSizeInBytes();
|
||||||
for (int i = 0; i < image.getWidth(); ++i) {
|
|
||||||
for (int j = 0; j < image.getHeight(); ++j) {
|
|
||||||
texelData.getRGBA8(i, j, pixelBytes);
|
|
||||||
bytes[(j * image.getWidth() + i) * 4] = pixelBytes[0];
|
|
||||||
bytes[(j * image.getWidth() + i) * 4 + 1] = pixelBytes[1];
|
|
||||||
bytes[(j * image.getWidth() + i) * 4 + 2] = pixelBytes[2];
|
|
||||||
bytes[(j * image.getWidth() + i) * 4 + 3] = pixelBytes[3];
|
|
||||||
}
|
}
|
||||||
|
byte[] bytes = new byte[resultSize];
|
||||||
|
int offset = 0;
|
||||||
|
byte[] pixelBytes = new byte[4];
|
||||||
|
for(DDSTexelData texelData : texelDataList) {
|
||||||
|
for (int i = 0; i < texelData.getPixelWidth(); ++i) {
|
||||||
|
for (int j = 0; j < texelData.getPixelHeight(); ++j) {
|
||||||
|
if(texelData.getRGBA8(i, j, pixelBytes)) {
|
||||||
|
bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0];
|
||||||
|
bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1];
|
||||||
|
bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2];
|
||||||
|
bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3];
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += texelData.getSizeInBytes();
|
||||||
}
|
}
|
||||||
dataArray.add(BufferUtils.createByteBuffer(bytes));
|
dataArray.add(BufferUtils.createByteBuffer(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) :
|
Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) :
|
||||||
new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0));
|
new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0));
|
||||||
if(image.getMipMapSizes() != null) {
|
if(newMipmapSizes != null) {
|
||||||
result.setMipMapSizes(image.getMipMapSizes().clone());
|
result.setMipMapSizes(newMipmapSizes);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -500,13 +521,15 @@ public class TextureHelper extends AbstractBlenderHelper {
|
|||||||
float gfac = ((Number) tex.getFieldValue("gfac")).floatValue();
|
float gfac = ((Number) tex.getFieldValue("gfac")).floatValue();
|
||||||
float bfac = ((Number) tex.getFieldValue("bfac")).floatValue();
|
float bfac = ((Number) tex.getFieldValue("bfac")).floatValue();
|
||||||
float[][] colorBand = new ColorBand(tex, blenderContext).computeValues();
|
float[][] colorBand = new ColorBand(tex, blenderContext).computeValues();
|
||||||
|
int depth = image.getDepth() == 0 ? 1 : image.getDepth();
|
||||||
|
|
||||||
if (colorBand != null) {
|
if (colorBand != null) {
|
||||||
TexturePixel pixel = new TexturePixel();
|
TexturePixel pixel = new TexturePixel();
|
||||||
PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat());
|
PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat());
|
||||||
|
for (int layerIndex = 0; layerIndex < depth; ++layerIndex) {
|
||||||
for (int x = 0; x < image.getWidth(); ++x) {
|
for (int x = 0; x < image.getWidth(); ++x) {
|
||||||
for (int y = 0; y < image.getHeight(); ++y) {
|
for (int y = 0; y < image.getHeight(); ++y) {
|
||||||
imageIO.read(image, pixel, x, y);
|
imageIO.read(image, layerIndex, pixel, x, y);
|
||||||
|
|
||||||
int colorbandIndex = (int) (pixel.alpha * 1000.0f);
|
int colorbandIndex = (int) (pixel.alpha * 1000.0f);
|
||||||
pixel.red = colorBand[colorbandIndex][0] * rfac;
|
pixel.red = colorBand[colorbandIndex][0] * rfac;
|
||||||
@ -514,21 +537,24 @@ public class TextureHelper extends AbstractBlenderHelper {
|
|||||||
pixel.blue = colorBand[colorbandIndex][2] * bfac;
|
pixel.blue = colorBand[colorbandIndex][2] * bfac;
|
||||||
pixel.alpha = colorBand[colorbandIndex][3];
|
pixel.alpha = colorBand[colorbandIndex][3];
|
||||||
|
|
||||||
imageIO.write(image, pixel, x, y);
|
imageIO.write(image, layerIndex, pixel, x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (rfac != 1.0f || gfac != 1.0f || bfac != 1.0f) {
|
} else if (rfac != 1.0f || gfac != 1.0f || bfac != 1.0f) {
|
||||||
TexturePixel pixel = new TexturePixel();
|
TexturePixel pixel = new TexturePixel();
|
||||||
PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat());
|
PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat());
|
||||||
|
for (int layerIndex = 0; layerIndex < depth; ++layerIndex) {
|
||||||
for (int x = 0; x < image.getWidth(); ++x) {
|
for (int x = 0; x < image.getWidth(); ++x) {
|
||||||
for (int y = 0; y < image.getHeight(); ++y) {
|
for (int y = 0; y < image.getHeight(); ++y) {
|
||||||
imageIO.read(image, pixel, x, y);
|
imageIO.read(image, layerIndex, pixel, x, y);
|
||||||
|
|
||||||
pixel.red *= rfac;
|
pixel.red *= rfac;
|
||||||
pixel.green *= gfac;
|
pixel.green *= gfac;
|
||||||
pixel.blue *= bfac;
|
pixel.blue *= bfac;
|
||||||
|
|
||||||
imageIO.write(image, pixel, x, y);
|
imageIO.write(image, layerIndex, pixel, x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,10 +184,10 @@ import com.jme3.util.BufferUtils;
|
|||||||
|
|
||||||
for (int x = 0; x < sourceImage.getWidth(); ++x) {
|
for (int x = 0; x < sourceImage.getWidth(); ++x) {
|
||||||
for (int y = 0; y < sourceImage.getHeight(); ++y) {
|
for (int y = 0; y < sourceImage.getHeight(); ++y) {
|
||||||
sourceIO.read(sourceImage, sourcePixel, x, y);
|
sourceIO.read(sourceImage, 0, sourcePixel, x, y);
|
||||||
targetIO.read(targetImage, targetPixel, x, y);
|
targetIO.read(targetImage, 0, targetPixel, x, y);
|
||||||
targetPixel.merge(sourcePixel);
|
targetPixel.merge(sourcePixel);
|
||||||
targetIO.write(targetImage, targetPixel, x, y);
|
targetIO.write(targetImage, 0, targetPixel, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,8 +331,8 @@ import com.jme3.util.BufferUtils;
|
|||||||
|
|
||||||
for (int x = 0; x < source.getWidth(); ++x) {
|
for (int x = 0; x < source.getWidth(); ++x) {
|
||||||
for (int y = 0; y < source.getHeight(); ++y) {
|
for (int y = 0; y < source.getHeight(); ++y) {
|
||||||
sourceIO.read(source, pixel, x, y);
|
sourceIO.read(source, 0, pixel, x, y);
|
||||||
targetIO.write(target, pixel, targetXPos + x, targetYPos + y);
|
targetIO.write(target, 0, pixel, targetXPos + x, targetYPos + y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -465,7 +465,7 @@ import com.jme3.util.BufferUtils;
|
|||||||
for (int x = minX; x < maxX; ++x) {
|
for (int x = minX; x < maxX; ++x) {
|
||||||
int xPos = x >= sourceImage.getWidth() ? x - sourceImage.getWidth() : x;
|
int xPos = x >= sourceImage.getWidth() ? x - sourceImage.getWidth() : x;
|
||||||
int yPos = y >= sourceImage.getHeight() ? y - sourceImage.getHeight() : y;
|
int yPos = y >= sourceImage.getHeight() ? y - sourceImage.getHeight() : y;
|
||||||
pixelReader.read(sourceImage, pixel, xPos, yPos);
|
pixelReader.read(sourceImage, 0, pixel, xPos, yPos);
|
||||||
data.put(pixel.getR8());
|
data.put(pixel.getR8());
|
||||||
data.put(pixel.getG8());
|
data.put(pixel.getG8());
|
||||||
data.put(pixel.getB8());
|
data.put(pixel.getB8());
|
||||||
@ -542,7 +542,7 @@ import com.jme3.util.BufferUtils;
|
|||||||
for (int y = 0; y < imageHeight; ++y) {
|
for (int y = 0; y < imageHeight; ++y) {
|
||||||
this.toTextureUV(boundingBox, point, uvs);
|
this.toTextureUV(boundingBox, point, uvs);
|
||||||
texture.getPixel(pixel, uvs[0], uvs[1], uvs[2]);
|
texture.getPixel(pixel, uvs[0], uvs[1], uvs[2]);
|
||||||
pixelWriter.write(image, pixel, x, y);
|
pixelWriter.write(image, 0, pixel, x, y);
|
||||||
point.addLocal(hDelta);
|
point.addLocal(hDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,11 @@ package com.jme3.scene.plugins.blender.textures.blending;
|
|||||||
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import jme3tools.converters.MipMapGenerator;
|
||||||
|
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
|
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
|
||||||
|
import com.jme3.texture.Image;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract class that contains the basic methods used by the classes that
|
* An abstract class that contains the basic methods used by the classes that
|
||||||
@ -112,4 +115,24 @@ import com.jme3.scene.plugins.blender.materials.MaterialHelper;
|
|||||||
LOGGER.warning("Cannot copy blending data from other types than " + this.getClass());
|
LOGGER.warning("Cannot copy blending data from other types than " + this.getClass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method prepares images for blending. It generates mipmaps if one of
|
||||||
|
* the images has them defined and the other one has not.
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* the image where the blending result is stored
|
||||||
|
* @param source
|
||||||
|
* the image that is being read only
|
||||||
|
*/
|
||||||
|
protected void prepareImagesForBlending(Image target, Image source) {
|
||||||
|
LOGGER.fine("Generating mipmaps if needed!");
|
||||||
|
boolean targetHasMipmaps = target == null ? false : target.getMipMapSizes() != null && target.getMipMapSizes().length > 0;
|
||||||
|
boolean sourceHasMipmaps = source == null ? false : source.getMipMapSizes() != null && source.getMipMapSizes().length > 0;
|
||||||
|
if (target != null && !targetHasMipmaps && sourceHasMipmaps) {
|
||||||
|
MipMapGenerator.generateMipMaps(target);
|
||||||
|
} else if (source != null && !sourceHasMipmaps && targetHasMipmaps) {
|
||||||
|
MipMapGenerator.generateMipMaps(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,8 @@ public class TextureBlenderAWT extends AbstractTextureBlender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
|
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
|
||||||
|
this.prepareImagesForBlending(image, baseImage);
|
||||||
|
|
||||||
float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f };
|
float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f };
|
||||||
Format format = image.getFormat();
|
Format format = image.getFormat();
|
||||||
|
|
||||||
@ -70,12 +72,12 @@ public class TextureBlenderAWT extends AbstractTextureBlender {
|
|||||||
while (index < data.limit()) {
|
while (index < data.limit()) {
|
||||||
//getting the proper material color if the base texture is applied
|
//getting the proper material color if the base texture is applied
|
||||||
if(basePixelIO != null) {
|
if(basePixelIO != null) {
|
||||||
basePixelIO.read(baseImage, basePixel, x, y);
|
basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y);
|
||||||
basePixel.toRGBA(materialColor);
|
basePixel.toRGBA(materialColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
//reading the current texture's pixel
|
//reading the current texture's pixel
|
||||||
pixelReader.read(image, pixel, index);
|
pixelReader.read(image, dataLayerIndex, pixel, index);
|
||||||
index += image.getFormat().getBitsPerPixel() >> 3;
|
index += image.getFormat().getBitsPerPixel() >> 3;
|
||||||
pixel.toRGBA(pixelColor);
|
pixel.toRGBA(pixelColor);
|
||||||
if (negateTexture) {
|
if (negateTexture) {
|
||||||
|
@ -28,6 +28,8 @@ public class TextureBlenderDDS extends TextureBlenderAWT {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
|
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
|
||||||
|
this.prepareImagesForBlending(image, baseImage);
|
||||||
|
|
||||||
Format format = image.getFormat();
|
Format format = image.getFormat();
|
||||||
int width = image.getWidth();
|
int width = image.getWidth();
|
||||||
int height = image.getHeight();
|
int height = image.getHeight();
|
||||||
@ -55,7 +57,6 @@ public class TextureBlenderDDS extends TextureBlenderAWT {
|
|||||||
ByteBuffer data = image.getData(dataLayerIndex);
|
ByteBuffer data = image.getData(dataLayerIndex);
|
||||||
data.rewind();
|
data.rewind();
|
||||||
ByteBuffer newData = BufferUtils.createByteBuffer(data.remaining());
|
ByteBuffer newData = BufferUtils.createByteBuffer(data.remaining());
|
||||||
System.out.println(dataLayerIndex);
|
|
||||||
while (data.hasRemaining()) {
|
while (data.hasRemaining()) {
|
||||||
if(format == Format.DXT3) {
|
if(format == Format.DXT3) {
|
||||||
long alpha = data.getLong();
|
long alpha = data.getLong();
|
||||||
@ -84,8 +85,8 @@ public class TextureBlenderDDS extends TextureBlenderAWT {
|
|||||||
//compressing 16 pixels from the base texture as if they belonged to a texel
|
//compressing 16 pixels from the base texture as if they belonged to a texel
|
||||||
if(baseImage != null) {
|
if(baseImage != null) {
|
||||||
//reading pixels (first and last of the 16 colors array)
|
//reading pixels (first and last of the 16 colors array)
|
||||||
basePixelIO.read(baseImage, baseTextureColors[0], baseXTexelIndex << 2, baseYTexelIndex << 2);//first pixel
|
basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[0], baseXTexelIndex << 2, baseYTexelIndex << 2);//first pixel
|
||||||
basePixelIO.read(baseImage, baseTextureColors[1], baseXTexelIndex << 2 + 4, baseYTexelIndex << 2 + 4);//last pixel
|
basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[1], baseXTexelIndex << 2 + 4, baseYTexelIndex << 2 + 4);//last pixel
|
||||||
baseTextureColors[0].toRGBA(compressedMaterialColor[0]);
|
baseTextureColors[0].toRGBA(compressedMaterialColor[0]);
|
||||||
baseTextureColors[1].toRGBA(compressedMaterialColor[1]);
|
baseTextureColors[1].toRGBA(compressedMaterialColor[1]);
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,9 @@ public class TextureBlenderLuminance extends AbstractTextureBlender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
|
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
|
||||||
Format format = image.getFormat();
|
this.prepareImagesForBlending(image, baseImage);
|
||||||
|
|
||||||
|
Format format = image.getFormat();
|
||||||
PixelInputOutput basePixelIO = null;
|
PixelInputOutput basePixelIO = null;
|
||||||
TexturePixel basePixel = null;
|
TexturePixel basePixel = null;
|
||||||
float[] materialColor = this.materialColor;
|
float[] materialColor = this.materialColor;
|
||||||
@ -64,7 +65,7 @@ public class TextureBlenderLuminance extends AbstractTextureBlender {
|
|||||||
while (data.hasRemaining()) {
|
while (data.hasRemaining()) {
|
||||||
//getting the proper material color if the base texture is applied
|
//getting the proper material color if the base texture is applied
|
||||||
if(basePixelIO != null) {
|
if(basePixelIO != null) {
|
||||||
basePixelIO.read(baseImage, basePixel, x, y);
|
basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y);
|
||||||
basePixel.toRGBA(materialColor);
|
basePixel.toRGBA(materialColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.jme3.scene.plugins.blender.textures.io;
|
package com.jme3.scene.plugins.blender.textures.io;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import com.jme3.scene.plugins.blender.textures.TexturePixel;
|
import com.jme3.scene.plugins.blender.textures.TexturePixel;
|
||||||
import com.jme3.texture.Image;
|
import com.jme3.texture.Image;
|
||||||
|
|
||||||
@ -8,25 +10,26 @@ import com.jme3.texture.Image;
|
|||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
*/
|
*/
|
||||||
/*package*/ class AWTPixelInputOutput implements PixelInputOutput {
|
/*package*/ class AWTPixelInputOutput implements PixelInputOutput {
|
||||||
public void read(Image image, TexturePixel pixel, int index) {
|
public void read(Image image, int layer, TexturePixel pixel, int index) {
|
||||||
byte r,g,b,a;
|
byte r,g,b,a;
|
||||||
|
ByteBuffer data = image.getData(layer);
|
||||||
switch(image.getFormat()) {//TODO: add other formats
|
switch(image.getFormat()) {//TODO: add other formats
|
||||||
case RGBA8:
|
case RGBA8:
|
||||||
r = image.getData(0).get(index);
|
r = data.get(index);
|
||||||
g = image.getData(0).get(index + 1);
|
g = data.get(index + 1);
|
||||||
b = image.getData(0).get(index + 2);
|
b = data.get(index + 2);
|
||||||
a = image.getData(0).get(index + 3);
|
a = data.get(index + 3);
|
||||||
break;
|
break;
|
||||||
case ABGR8:
|
case ABGR8:
|
||||||
a = image.getData(0).get(index);
|
a = data.get(index);
|
||||||
b = image.getData(0).get(index + 1);
|
b = data.get(index + 1);
|
||||||
g = image.getData(0).get(index + 2);
|
g = data.get(index + 2);
|
||||||
r = image.getData(0).get(index + 3);
|
r = data.get(index + 3);
|
||||||
break;
|
break;
|
||||||
case BGR8:
|
case BGR8:
|
||||||
b = image.getData(0).get(index);
|
b = data.get(index);
|
||||||
g = image.getData(0).get(index + 1);
|
g = data.get(index + 1);
|
||||||
r = image.getData(0).get(index + 2);
|
r = data.get(index + 2);
|
||||||
a = (byte)0xFF;
|
a = (byte)0xFF;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -35,37 +38,38 @@ import com.jme3.texture.Image;
|
|||||||
pixel.fromARGB8(a, r, g, b);
|
pixel.fromARGB8(a, r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void read(Image image, TexturePixel pixel, int x, int y) {
|
public void read(Image image, int layer, TexturePixel pixel, int x, int y) {
|
||||||
int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3);
|
int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3);
|
||||||
this.read(image, pixel, index);
|
this.read(image, layer, pixel, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(Image image, TexturePixel pixel, int index) {
|
public void write(Image image, int layer, TexturePixel pixel, int index) {
|
||||||
|
ByteBuffer data = image.getData(layer);
|
||||||
switch(image.getFormat()) {
|
switch(image.getFormat()) {
|
||||||
case RGBA8:
|
case RGBA8:
|
||||||
image.getData(0).put(index, pixel.getR8());
|
data.put(index, pixel.getR8());
|
||||||
image.getData(0).put(index + 1, pixel.getG8());
|
data.put(index + 1, pixel.getG8());
|
||||||
image.getData(0).put(index + 2, pixel.getB8());
|
data.put(index + 2, pixel.getB8());
|
||||||
image.getData(0).put(index + 3, pixel.getA8());
|
data.put(index + 3, pixel.getA8());
|
||||||
break;
|
break;
|
||||||
case ABGR8:
|
case ABGR8:
|
||||||
image.getData(0).put(index, pixel.getA8());
|
data.put(index, pixel.getA8());
|
||||||
image.getData(0).put(index + 1, pixel.getB8());
|
data.put(index + 1, pixel.getB8());
|
||||||
image.getData(0).put(index + 2, pixel.getG8());
|
data.put(index + 2, pixel.getG8());
|
||||||
image.getData(0).put(index + 3, pixel.getR8());
|
data.put(index + 3, pixel.getR8());
|
||||||
break;
|
break;
|
||||||
case BGR8:
|
case BGR8:
|
||||||
image.getData(0).put(index, pixel.getB8());
|
data.put(index, pixel.getB8());
|
||||||
image.getData(0).put(index + 1, pixel.getG8());
|
data.put(index + 1, pixel.getG8());
|
||||||
image.getData(0).put(index + 2, pixel.getR8());
|
data.put(index + 2, pixel.getR8());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Unknown image format: " + image.getFormat());
|
throw new IllegalStateException("Unknown image format: " + image.getFormat());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(Image image, TexturePixel pixel, int x, int y) {
|
public void write(Image image, int layer, TexturePixel pixel, int x, int y) {
|
||||||
int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3);
|
int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3);
|
||||||
this.write(image, pixel, index);
|
this.write(image, layer, pixel, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,11 @@ import com.jme3.texture.Image;
|
|||||||
/**
|
/**
|
||||||
* For this class the index should be considered as a pixel index in AWT image format.
|
* For this class the index should be considered as a pixel index in AWT image format.
|
||||||
*/
|
*/
|
||||||
public void read(Image image, TexturePixel pixel, int index) {
|
public void read(Image image, int layer, TexturePixel pixel, int index) {
|
||||||
this.read(image, pixel, index % image.getWidth(), index / image.getWidth());
|
this.read(image, layer, pixel, index % image.getWidth(), index / image.getWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void read(Image image, TexturePixel pixel, int x, int y) {
|
public void read(Image image, int layer, TexturePixel pixel, int x, int y) {
|
||||||
int xTexetlIndex = x % image.getWidth() >> 2;
|
int xTexetlIndex = x % image.getWidth() >> 2;
|
||||||
int yTexelIndex = y % image.getHeight() >> 2;
|
int yTexelIndex = y % image.getHeight() >> 2;
|
||||||
int xTexelCount = image.getWidth() >> 2;
|
int xTexelCount = image.getWidth() >> 2;
|
||||||
@ -31,7 +31,7 @@ import com.jme3.texture.Image;
|
|||||||
int indexes = 0;
|
int indexes = 0;
|
||||||
long alphaIndexes = 0;
|
long alphaIndexes = 0;
|
||||||
float[] alphas = null;
|
float[] alphas = null;
|
||||||
ByteBuffer data = image.getData().get(0);
|
ByteBuffer data = image.getData().get(layer);
|
||||||
|
|
||||||
switch (image.getFormat()) {
|
switch (image.getFormat()) {
|
||||||
case DXT1: // BC1
|
case DXT1: // BC1
|
||||||
@ -162,11 +162,11 @@ import com.jme3.texture.Image;
|
|||||||
pixel.alpha = alpha;
|
pixel.alpha = alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(Image image, TexturePixel pixel, int index) {
|
public void write(Image image, int layer, TexturePixel pixel, int index) {
|
||||||
throw new UnsupportedOperationException("Cannot put the DXT pixel by index because not every index contains the pixel color!");
|
throw new UnsupportedOperationException("Cannot put the DXT pixel by index because not every index contains the pixel color!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(Image image, TexturePixel pixel, int x, int y) {
|
public void write(Image image, int layer, TexturePixel pixel, int x, int y) {
|
||||||
throw new UnsupportedOperationException("Writing to DDS texture pixel by pixel is not yet supported!");
|
throw new UnsupportedOperationException("Writing to DDS texture pixel by pixel is not yet supported!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,22 +8,22 @@ import com.jme3.texture.Image;
|
|||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
*/
|
*/
|
||||||
/*package*/ class LuminancePixelInputOutput implements PixelInputOutput {
|
/*package*/ class LuminancePixelInputOutput implements PixelInputOutput {
|
||||||
public void read(Image image, TexturePixel pixel, int index) {
|
public void read(Image image, int layer, TexturePixel pixel, int index) {
|
||||||
byte intensity = image.getData(0).get(index);
|
byte intensity = image.getData(layer).get(index);
|
||||||
pixel.fromIntensity(intensity);
|
pixel.fromIntensity(intensity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void read(Image image, TexturePixel pixel, int x, int y) {
|
public void read(Image image, int layer, TexturePixel pixel, int x, int y) {
|
||||||
int index = y * image.getWidth() + x;
|
int index = y * image.getWidth() + x;
|
||||||
this.read(image, pixel, index);
|
this.read(image, layer, pixel, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(Image image, TexturePixel pixel, int index) {
|
public void write(Image image, int layer, TexturePixel pixel, int index) {
|
||||||
image.getData(0).put(index, pixel.getInt());
|
image.getData(layer).put(index, pixel.getInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(Image image, TexturePixel pixel, int x, int y) {
|
public void write(Image image, int layer, TexturePixel pixel, int x, int y) {
|
||||||
int index = y * image.getWidth() + x;
|
int index = y * image.getWidth() + x;
|
||||||
this.write(image, pixel, index);
|
this.write(image, layer,pixel, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ public interface PixelInputOutput {
|
|||||||
* @param index
|
* @param index
|
||||||
* the index where the pixel begins in the image data
|
* the index where the pixel begins in the image data
|
||||||
*/
|
*/
|
||||||
void read(Image image, TexturePixel pixel, int index);
|
void read(Image image, int layer, TexturePixel pixel, int index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method reads a pixel that is located at the given position on the
|
* This method reads a pixel that is located at the given position on the
|
||||||
@ -34,7 +34,7 @@ public interface PixelInputOutput {
|
|||||||
* @param y
|
* @param y
|
||||||
* the Y coordinate of the pixel
|
* the Y coordinate of the pixel
|
||||||
*/
|
*/
|
||||||
void read(Image image, TexturePixel pixel, int x, int y);
|
void read(Image image, int layer, TexturePixel pixel, int x, int y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method writes a pixel that starts at the given index.
|
* This method writes a pixel that starts at the given index.
|
||||||
@ -46,7 +46,7 @@ public interface PixelInputOutput {
|
|||||||
* @param index
|
* @param index
|
||||||
* the index where the pixel begins in the image data
|
* the index where the pixel begins in the image data
|
||||||
*/
|
*/
|
||||||
void write(Image image, TexturePixel pixel, int index);
|
void write(Image image, int layer, TexturePixel pixel, int index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method writes a pixel that is located at the given position on the
|
* This method writes a pixel that is located at the given position on the
|
||||||
@ -61,5 +61,5 @@ public interface PixelInputOutput {
|
|||||||
* @param y
|
* @param y
|
||||||
* the Y coordinate of the pixel
|
* the Y coordinate of the pixel
|
||||||
*/
|
*/
|
||||||
void write(Image image, TexturePixel pixel, int x, int y);
|
void write(Image image, int layer, TexturePixel pixel, int x, int y);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user