blob: ff90f03b08cddccc81fdaf0a7014469ee6e7c662 [file] [log] [blame]
/*
* Copyright (c) 2009-2010 jMonkeyEngine
* 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 'jMonkeyEngine' 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 THE COPYRIGHT OWNER OR
* CONTRIBUTORS 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.jme3.texture.plugins;
import com.jme3.math.FastMath;
import com.jme3.texture.Image.Format;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* DXTFlipper is a utility class used to flip along Y axis DXT compressed textures.
*
* @author Kirill Vainer
*/
public class DXTFlipper {
private static final ByteBuffer bb = ByteBuffer.allocate(8);
static {
bb.order(ByteOrder.LITTLE_ENDIAN);
}
private static long readCode5(long data, int x, int y){
long shift = (4 * y + x) * 3;
long mask = 0x7;
mask <<= shift;
long code = data & mask;
code >>= shift;
return code;
}
private static long writeCode5(long data, int x, int y, long code){
long shift = (4 * y + x) * 3;
long mask = 0x7;
code = (code & mask) << shift;
mask <<= shift;
mask = ~mask;
data &= mask;
data |= code; // write new code
return data;
}
private static void flipDXT5Block(byte[] block, int h){
if (h == 1)
return;
byte c0 = block[0];
byte c1 = block[1];
bb.clear();
bb.put(block, 2, 6).flip();
bb.clear();
long l = bb.getLong();
long n = l;
if (h == 2){
n = writeCode5(n, 0, 0, readCode5(l, 0, 1));
n = writeCode5(n, 1, 0, readCode5(l, 1, 1));
n = writeCode5(n, 2, 0, readCode5(l, 2, 1));
n = writeCode5(n, 3, 0, readCode5(l, 3, 1));
n = writeCode5(n, 0, 1, readCode5(l, 0, 0));
n = writeCode5(n, 1, 1, readCode5(l, 1, 0));
n = writeCode5(n, 2, 1, readCode5(l, 2, 0));
n = writeCode5(n, 3, 1, readCode5(l, 3, 0));
}else{
n = writeCode5(n, 0, 0, readCode5(l, 0, 3));
n = writeCode5(n, 1, 0, readCode5(l, 1, 3));
n = writeCode5(n, 2, 0, readCode5(l, 2, 3));
n = writeCode5(n, 3, 0, readCode5(l, 3, 3));
n = writeCode5(n, 0, 1, readCode5(l, 0, 2));
n = writeCode5(n, 1, 1, readCode5(l, 1, 2));
n = writeCode5(n, 2, 1, readCode5(l, 2, 2));
n = writeCode5(n, 3, 1, readCode5(l, 3, 2));
n = writeCode5(n, 0, 2, readCode5(l, 0, 1));
n = writeCode5(n, 1, 2, readCode5(l, 1, 1));
n = writeCode5(n, 2, 2, readCode5(l, 2, 1));
n = writeCode5(n, 3, 2, readCode5(l, 3, 1));
n = writeCode5(n, 0, 3, readCode5(l, 0, 0));
n = writeCode5(n, 1, 3, readCode5(l, 1, 0));
n = writeCode5(n, 2, 3, readCode5(l, 2, 0));
n = writeCode5(n, 3, 3, readCode5(l, 3, 0));
}
bb.clear();
bb.putLong(n);
bb.clear();
bb.get(block, 2, 6).flip();
assert c0 == block[0] && c1 == block[1];
}
private static void flipDXT3Block(byte[] block, int h){
if (h == 1)
return;
// first row
byte tmp0 = block[0];
byte tmp1 = block[1];
if (h == 2){
block[0] = block[2];
block[1] = block[3];
block[2] = tmp0;
block[3] = tmp1;
}else{
// write last row to first row
block[0] = block[6];
block[1] = block[7];
// write first row to last row
block[6] = tmp0;
block[7] = tmp1;
// 2nd row
tmp0 = block[2];
tmp1 = block[3];
// write 3rd row to 2nd
block[2] = block[4];
block[3] = block[5];
// write 2nd row to 3rd
block[4] = tmp0;
block[5] = tmp1;
}
}
/**
* Flips a DXT color block or a DXT3 alpha block
* @param block
* @param h
*/
private static void flipDXT1orDXTA3Block(byte[] block, int h){
byte tmp;
switch (h){
case 1:
return;
case 2:
// keep header intact (the two colors)
// header takes 4 bytes
// flip only two top rows
tmp = block[4+1];
block[4+1] = block[4+0];
block[4+0] = tmp;
return;
default:
// keep header intact (the two colors)
// header takes 4 bytes
// flip first & fourth row
tmp = block[4+3];
block[4+3] = block[4+0];
block[4+0] = tmp;
// flip second and third row
tmp = block[4+2];
block[4+2] = block[4+1];
block[4+1] = tmp;
return;
}
}
public static ByteBuffer flipDXT(ByteBuffer img, int w, int h, Format format){
int blocksX = (int) FastMath.ceil((float)w / 4f);
int blocksY = (int) FastMath.ceil((float)h / 4f);
int type;
switch (format){
case DXT1:
case DXT1A:
type = 1;
break;
case DXT3:
type = 2;
break;
case DXT5:
type = 3;
break;
case LATC:
type = 4;
break;
case LTC:
type = 5;
break;
default:
throw new IllegalArgumentException();
}
// DXT1 uses 8 bytes per block,
// DXT3, DXT5, LATC use 16 bytes per block
int bpb = type == 1 || type == 5 ? 8 : 16;
ByteBuffer retImg = BufferUtils.createByteBuffer(blocksX * blocksY * bpb);
if (h == 1){
retImg.put(img);
retImg.rewind();
return retImg;
}else if (h == 2){
byte[] colorBlock = new byte[8];
byte[] alphaBlock = type != 1 && type != 5 ? new byte[8] : null;
for (int x = 0; x < blocksX; x++){
// prepeare for block reading
int blockByteOffset = x * bpb;
img.position(blockByteOffset);
img.limit(blockByteOffset + bpb);
img.get(colorBlock);
if (type == 4 || type == 5)
flipDXT5Block(colorBlock, h);
else
flipDXT1orDXTA3Block(colorBlock, h);
// write block (no need to flip block indexes, only pixels
// inside block
retImg.put(colorBlock);
if (alphaBlock != null){
img.get(alphaBlock);
switch (type){
case 2:
flipDXT3Block(alphaBlock, h); break;
case 3:
case 4:
flipDXT5Block(alphaBlock, h);
break;
}
retImg.put(alphaBlock);
}
}
retImg.rewind();
return retImg;
}else if (h >= 4){
byte[] colorBlock = new byte[8];
byte[] alphaBlock = type != 1 && type != 5 ? new byte[8] : null;
for (int y = 0; y < blocksY; y++){
for (int x = 0; x < blocksX; x++){
// prepeare for block reading
int blockIdx = y * blocksX + x;
int blockByteOffset = blockIdx * bpb;
img.position(blockByteOffset);
img.limit(blockByteOffset + bpb);
blockIdx = (blocksY - y - 1) * blocksX + x;
blockByteOffset = blockIdx * bpb;
retImg.position(blockByteOffset);
retImg.limit(blockByteOffset + bpb);
if (alphaBlock != null){
img.get(alphaBlock);
switch (type){
case 2:
flipDXT3Block(alphaBlock, h);
break;
case 3:
case 4:
flipDXT5Block(alphaBlock, h);
break;
}
retImg.put(alphaBlock);
}
img.get(colorBlock);
if (type == 4 || type == 5)
flipDXT5Block(colorBlock, h);
else
flipDXT1orDXTA3Block(colorBlock, h);
retImg.put(colorBlock);
}
}
retImg.limit(retImg.capacity());
retImg.position(0);
return retImg;
}else{
return null;
}
}
}