Package info.ata4.unity.cli.extract

Source Code of info.ata4.unity.cli.extract.Texture2DHandler

/*
** 2013 June 16
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
*/
package info.ata4.unity.cli.extract;

import info.ata4.unity.engine.Texture2D;
import info.ata4.io.DataOutputWriter;
import info.ata4.io.buffer.ByteBufferUtils;
import info.ata4.log.LogUtils;
import info.ata4.unity.asset.struct.ObjectPath;
import info.ata4.unity.engine.enums.TextureFormat;
import static info.ata4.unity.engine.enums.TextureFormat.*;
import info.ata4.unity.serdes.UnityObject;
import info.ata4.util.io.image.dds.DDSHeader;
import info.ata4.util.io.image.dds.DDSPixelFormat;
import info.ata4.util.io.image.ktx.KTXHeader;
import info.ata4.util.io.image.tga.TGAHeader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
*
* @author Nico Bergemann <barracuda415 at yahoo.de>
*/
public class Texture2DHandler extends AssetExtractHandler {
   
    private static final Logger L = LogUtils.getLogger();
   
    private ObjectPath path;
    private Texture2D tex;
    private boolean tgaSaveMipMaps = true;
   
    public boolean isTargaSaveMipMaps() {
        return tgaSaveMipMaps;
    }

    public void setTargaSaveMipMaps(boolean tgaSaveMipMaps) {
        this.tgaSaveMipMaps = tgaSaveMipMaps;
    }
   
    @Override
    public void extract(UnityObject obj) throws IOException {
        this.path = path;

        try {
            // create Texture2D from serialized object
            tex = new Texture2D(obj);
        } catch (RuntimeException ex) {
            L.log(Level.WARNING, "Deserialization error", ex);
            return;
        }
       
        if (tex.textureFormat == null) {
            L.log(Level.WARNING, "Texture2D {0}: Unknown texture format {1}",
                    new Object[] {tex.name, tex.textureFormatOrd});
            return;
        }

        // some textures (font textures?) don't have any image data, not sure why...
        if (tex.imageBuffer.capacity() == 0) {
            L.log(Level.WARNING, "Texture2D {0}: Empty image buffer", tex.name);
            return;
        }
       
        // choose a fitting container format
        switch (tex.textureFormat) {
            case Alpha8:
            case RGB24:
            case RGBA32:
            case BGRA32:
            case ARGB32:
            case ARGB4444:
            case RGBA4444:
            case RGB565:
                extractTGA();
                break;
           
            case PVRTC_RGB2:
            case PVRTC_RGBA2:
            case PVRTC_RGB4:
            case PVRTC_RGBA4:
            case ATC_RGB4:
            case ATC_RGBA8:
            case ETC_RGB4:
            case ETC2_RGB4:
            case ETC2_RGB4_PUNCHTHROUGH_ALPHA:
            case ETC2_RGBA8:
            case EAC_R:
            case EAC_R_SIGNED:
            case EAC_RG:
            case EAC_RG_SIGNED:
                extractKTX();
                break;

            case DXT1:
            case DXT5:
                extractDDS();
                break;
               
            default:
                L.log(Level.WARNING, "Texture2D {0}: Unsupported texture format {1}",
                        new Object[] {tex.name, tex.textureFormat});
        }
    }
   
    private int getMipMapCount(int width, int height) {
        int mipMapCount = 1;
        for (int dim = Math.max(width, height); dim > 1; dim /= 2) {
            mipMapCount++;
        }
        return mipMapCount;
    }
   
    private void extractDDS() throws IOException {
        DDSHeader header = new DDSHeader();
        header.dwWidth = tex.width;
        header.dwHeight = tex.height;

        switch (tex.textureFormat) {
            case Alpha8:
                header.ddspf.dwFlags = DDSPixelFormat.DDPF_ALPHA;
                header.ddspf.dwABitMask = 0xff;
                header.ddspf.dwRGBBitCount = 8;
                break;
               
            case RGB24:
                header.ddspf.dwFlags = DDSPixelFormat.DDPF_RGB;
                header.ddspf.dwRBitMask = 0xff0000;
                header.ddspf.dwGBitMask = 0x00ff00;
                header.ddspf.dwBBitMask = 0x0000ff;
                header.ddspf.dwRGBBitCount = 24;
                break;
               
            case RGBA32:
                header.ddspf.dwFlags = DDSPixelFormat.DDPF_RGBA;
                header.ddspf.dwRBitMask = 0x000000ff;
                header.ddspf.dwGBitMask = 0x0000ff00;
                header.ddspf.dwBBitMask = 0x00ff0000;
                header.ddspf.dwABitMask = 0xff000000;
                header.ddspf.dwRGBBitCount = 32;
                break;
               
            case BGRA32:
                header.ddspf.dwFlags = DDSPixelFormat.DDPF_RGBA;
                header.ddspf.dwRBitMask = 0x00ff0000;
                header.ddspf.dwGBitMask = 0x0000ff00;
                header.ddspf.dwBBitMask = 0x000000ff;
                header.ddspf.dwABitMask = 0xff000000;
                header.ddspf.dwRGBBitCount = 32;
                break;
               
            case ARGB32:
                header.ddspf.dwFlags = DDSPixelFormat.DDPF_RGBA;
                header.ddspf.dwRBitMask = 0x0000ff00;
                header.ddspf.dwGBitMask = 0x00ff0000;
                header.ddspf.dwBBitMask = 0xff000000;
                header.ddspf.dwABitMask = 0x000000ff;
                header.ddspf.dwRGBBitCount = 32;
                break;
                   
            case ARGB4444:
                header.ddspf.dwFlags = DDSPixelFormat.DDPF_RGBA;
                header.ddspf.dwRBitMask = 0x0f00;
                header.ddspf.dwGBitMask = 0x00f0;
                header.ddspf.dwBBitMask = 0x000f;
                header.ddspf.dwABitMask = 0xf000;
                header.ddspf.dwRGBBitCount = 16;
                break;
               
            case RGBA4444:
                header.ddspf.dwFlags = DDSPixelFormat.DDPF_RGBA;
                header.ddspf.dwRBitMask = 0xf000;
                header.ddspf.dwGBitMask = 0x0f00;
                header.ddspf.dwBBitMask = 0x00f0;
                header.ddspf.dwABitMask = 0x000f;
                header.ddspf.dwRGBBitCount = 16;
                break;
               
            case RGB565:
                header.ddspf.dwFlags = DDSPixelFormat.DDPF_RGB;
                header.ddspf.dwRBitMask = 0xf800;
                header.ddspf.dwGBitMask = 0x07e0;
                header.ddspf.dwBBitMask = 0x001f;
                header.ddspf.dwRGBBitCount = 16;
                break;
           
            case DXT1:
                header.ddspf.dwFourCC = DDSPixelFormat.PF_DXT1;
                break;
           
            case DXT5:
                header.ddspf.dwFourCC = DDSPixelFormat.PF_DXT5;
                break;
               
            default:
                throw new IllegalStateException("Invalid texture format for DDS: " + tex.textureFormat);
        }

        // set mip map flags if required
        if (tex.mipMap) {
            header.dwFlags |= DDSHeader.DDS_HEADER_FLAGS_MIPMAP;
            header.dwCaps |= DDSHeader.DDS_SURFACE_FLAGS_MIPMAP;
            header.dwMipMapCount = getMipMapCount(header.dwWidth, header.dwHeight);
        }
       
        // set and calculate linear size
        header.dwFlags |= DDSHeader.DDS_HEADER_FLAGS_LINEARSIZE;
        if (header.ddspf.dwFourCC != 0) {
            header.dwPitchOrLinearSize = header.dwWidth * header.dwHeight;
           
            if (tex.textureFormat == TextureFormat.DXT1) {
                header.dwPitchOrLinearSize /= 2;
            }
           
            header.ddspf.dwFlags |= DDSPixelFormat.DDPF_FOURCC;
        } else {
            header.dwPitchOrLinearSize = (tex.width * tex.height * header.ddspf.dwRGBBitCount) / 8;
        }
       
        ByteBuffer bbTex = ByteBuffer.allocateDirect(DDSHeader.SIZE + tex.imageBuffer.capacity());
        bbTex.order(ByteOrder.LITTLE_ENDIAN);
       
        // write header
        DataOutputWriter out = DataOutputWriter.newWriter(bbTex);
        header.write(out);
       
        // write data
        bbTex.put(tex.imageBuffer);
       
        bbTex.rewind();
       
        setOutputFileName(tex.name);
        setOutputFileExtension("dds");
        writeData(bbTex);
    }

    private void extractPKM() throws IOException {
        // texWidth and texHeight are width and height rounded up to multiple of 4.
        int texWidth = ((tex.width - 1) | 3) + 1;
        int texHeight = ((tex.height - 1) | 3) + 1;

        ByteBuffer res = ByteBuffer.allocateDirect(16 + tex.imageBuffer.capacity());
        res.order(ByteOrder.BIG_ENDIAN);

        res.putLong(0x504b4d2031300000L); // PKM 10\0\0

        res.putShort((short) texWidth);
        res.putShort((short) texHeight);
        res.putShort(tex.width.shortValue());
        res.putShort(tex.height.shortValue());

        res.put(tex.imageBuffer);

        res.rewind();

        setOutputFileName(tex.name);
        setOutputFileExtension("pkm");
        writeData(res);
    }

    private void extractTGA() throws IOException {
        TGAHeader header = new TGAHeader();
       
        switch (tex.textureFormat) {
            case Alpha8:
                header.imageType = 3;
                header.pixelDepth = 8;
                break;

            case RGBA32:
            case ARGB32:
            case BGRA32:
            case RGBA4444:
            case ARGB4444:
                header.imageType = 2;
                header.pixelDepth = 32;
                break;

            case RGB24:
            case RGB565:
                header.imageType = 2;
                header.pixelDepth = 24;
                break;

            default:
                throw new IllegalStateException("Invalid texture format for TGA: " + tex.textureFormat);
        }
       
        convertToRGBA32();
       
        ByteBuffer bb = tex.imageBuffer;

        int mipMapCount = 1;
       
        if (tex.mipMap) {
            mipMapCount = getMipMapCount(tex.width, tex.height);
        }
       
        for (int i = 0; i < tex.imageCount; i++) {
            header.imageWidth = tex.width;
            header.imageHeight = tex.height;
           
            for (int j = 0; j < mipMapCount; j++) {
                int imageSize = header.imageWidth * header.imageHeight * header.pixelDepth / 8;
               
                if (tgaSaveMipMaps || j == 0) {
                    ByteBuffer bbTga = ByteBuffer.allocateDirect(TGAHeader.SIZE + imageSize);
                    bbTga.order(ByteOrder.LITTLE_ENDIAN);

                    // write TGA header
                    DataOutputWriter out = DataOutputWriter.newWriter(bbTga);
                    header.write(out);

                    // write image data
                    bb.limit(bb.position() + imageSize);
                    bbTga.put(bb);
                    bb.limit(bb.capacity());

                    assert !bbTga.hasRemaining();

                    // write file
                    bbTga.rewind();

                    String fileName = tex.name;

                    if (tex.imageCount > 1) {
                        fileName += "_" + i;
                    }

                    if (tex.mipMap && tgaSaveMipMaps) {
                        fileName += "_mip_" + j;
                    }

                    setOutputFileName(fileName);
                    setOutputFileExtension("tga");
                    writeData(bbTga);
                } else {
                    bb.position(bb.position() + imageSize);
                }

                // prepare for the next mip map
                if (header.imageWidth > 1) {
                    header.imageWidth /= 2;
                }
                if (header.imageHeight > 1) {
                    header.imageHeight /= 2;
                }
            }
        }

        assert !bb.hasRemaining();
    }
   
    private void convertToRGBA32() {
        ByteBuffer imageBuffer = tex.imageBuffer;
        TextureFormat tf = tex.textureFormat;
       
        if (tf == RGBA32 || tf == ARGB32) {
            // convert ARGB and RGBA directly by swapping the bytes to get BGRA
            byte[] pixelOld = new byte[4];
            byte[] pixelNew = new byte[4];
            for (int i = 0; i < imageBuffer.capacity() / 4; i++) {
                imageBuffer.mark();
                imageBuffer.get(pixelOld);
               
                if (tf == ARGB32) {
                    // ARGB -> BGRA
                    pixelNew[0] = pixelOld[3];
                    pixelNew[1] = pixelOld[2];
                    pixelNew[2] = pixelOld[1];
                    pixelNew[3] = pixelOld[0];
                } else {
                    // RGBA -> BGRA
                    pixelNew[0] = pixelOld[2];
                    pixelNew[1] = pixelOld[1];
                    pixelNew[2] = pixelOld[0];
                    pixelNew[3] = pixelOld[3];
                }
               
                imageBuffer.reset();
                imageBuffer.put(pixelNew);
            }

            imageBuffer.rewind();
        } if (tf == RGB24) {
            // convert RGB directly to BGR
            byte[] pixelOld = new byte[3];
            byte[] pixelNew = new byte[3];
            for (int i = 0; i < imageBuffer.capacity() / 3; i++) {
                imageBuffer.mark();
                imageBuffer.get(pixelOld);
               
                pixelNew[0] = pixelOld[2];
                pixelNew[1] = pixelOld[1];
                pixelNew[2] = pixelOld[0];
               
                imageBuffer.reset();
                imageBuffer.put(pixelNew);
            }

            imageBuffer.rewind();
        } else if (tf == ARGB4444 || tf == RGBA4444) {
            // convert 16 bit RGBA/ARGB to 32 bit BGRA
            int newImageSize = imageBuffer.capacity() * 2;
            ByteBuffer imageBufferNew = ByteBuffer.allocateDirect(newImageSize);
           
            byte[] pixelOld = new byte[4];
            byte[] pixelNew = new byte[4];
            for (int i = 0; i < imageBuffer.capacity() / 2; i++) {
                int pixelOldShort = imageBuffer.getShort();
               
                pixelOld[0] = (byte) ((pixelOldShort & 0xf000) >> 12);
                pixelOld[1] = (byte) ((pixelOldShort & 0x0f00) >> 8);
                pixelOld[2] = (byte) ((pixelOldShort & 0x00f0) >> 4);
                pixelOld[3] = (byte)  (pixelOldShort & 0x000f);
               
                // convert range
                pixelOld[0] <<= 4;
                pixelOld[1] <<= 4;
                pixelOld[2] <<= 4;
                pixelOld[3] <<= 4;
               
                if (tf == ARGB4444) {
                    // ARBG -> BGRA
                    pixelNew[0] = pixelOld[3];
                    pixelNew[1] = pixelOld[2];
                    pixelNew[2] = pixelOld[1];
                    pixelNew[3] = pixelOld[0];
                } else {
                    // RBGA -> BGRA
                    pixelNew[0] = pixelOld[2];
                    pixelNew[1] = pixelOld[1];
                    pixelNew[2] = pixelOld[0];
                    pixelNew[3] = pixelOld[3];
                }
               
                imageBufferNew.put(pixelNew);
            }
           
            assert !imageBuffer.hasRemaining();
            assert !imageBufferNew.hasRemaining();
           
            imageBufferNew.rewind();
            imageBuffer = imageBufferNew;
        } else if (tf == RGB565) {
            // convert 16 bit RGB to 24 bit
            int newImageSize = (imageBuffer.capacity() / 2) * 3;
            ByteBuffer imageBufferNew = ByteBuffer.allocateDirect(newImageSize);
           
            byte[] pixel = new byte[3];
            for (int i = 0; i < imageBuffer.capacity() / 2; i++) {
                short pixelOld = imageBuffer.getShort();

                pixel[0] = (byte) ((pixelOld & 0xf800) >> 11);
                pixel[1] = (byte) ((pixelOld & 0x07e0) >> 5);
                pixel[2] = (byte)  (pixelOld & 0x001f);

                // fix color mapping (http://stackoverflow.com/a/9069480)
                pixel[0] = (byte) ((pixel[0] * 527 + 23) >> 6);
                pixel[1] = (byte) ((pixel[1] * 259 + 33) >> 6);
                pixel[2] = (byte) ((pixel[2] * 527 + 23) >> 6);
               
                imageBufferNew.put(pixel);
            }
           
            assert !imageBuffer.hasRemaining();
            assert !imageBufferNew.hasRemaining();
           
            imageBufferNew.rewind();
            imageBuffer = imageBufferNew;
        }
       
        tex.imageBuffer = imageBuffer;
    }
   
    private void extractKTX() throws IOException {
        KTXHeader header = new KTXHeader();
        header.swap = true;
        header.glTypeSize = 1;
        header.pixelWidth = tex.width;
        header.pixelHeight = tex.height;
        header.pixelDepth = 0;
        header.numberOfFaces = tex.imageCount;
        header.numberOfMipmapLevels = tex.mipMap ? getMipMapCount(header.pixelWidth, header.pixelHeight) : 1;
        int bpp;
       
        switch (tex.textureFormat) {
            case PVRTC_RGB2:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
                header.glBaseInternalFormat = KTXHeader.GL_RGB;
                bpp = 2;
                break;
               
            case PVRTC_RGBA2:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
                header.glBaseInternalFormat = KTXHeader.GL_RGBA;
                bpp = 2;
                break;

            case PVRTC_RGB4:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
                header.glBaseInternalFormat = KTXHeader.GL_RGB;
                bpp = 4;
                break;
               
            case PVRTC_RGBA4:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
                header.glBaseInternalFormat = KTXHeader.GL_RGBA;
                bpp = 4;
                break;
               
            case ATC_RGB4:
                header.glInternalFormat = KTXHeader.GL_ATC_RGB_AMD;
                header.glBaseInternalFormat = KTXHeader.GL_RGB;
                bpp = 4;
                break;

            case ATC_RGBA8:
                header.glInternalFormat = KTXHeader.GL_ATC_RGBA_EXPLICIT_ALPHA_AMD;
                header.glBaseInternalFormat = KTXHeader.GL_RGBA;
                bpp = 8;
                break;
               
            case ETC_RGB4:
                header.glInternalFormat = KTXHeader.GL_ETC1_RGB8_OES;
                header.glBaseInternalFormat = KTXHeader.GL_RGB;
                bpp = 4;
                break;
               
            case ETC2_RGB4:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_RGB8_ETC2;
                header.glBaseInternalFormat = KTXHeader.GL_RGB;
                bpp = 4;
                break;
               
            case ETC2_RGB4_PUNCHTHROUGH_ALPHA:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
                header.glBaseInternalFormat = KTXHeader.GL_RGBA;
                bpp = 4;
                break;
               
            case ETC2_RGBA8:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_RGBA8_ETC2_EAC;
                header.glBaseInternalFormat = KTXHeader.GL_RGBA;
                bpp = 8;
                break;
        
            case EAC_R:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_R11_EAC;
                header.glBaseInternalFormat = KTXHeader.GL_RED;
                bpp = 4;
                break;
               
            case EAC_R_SIGNED:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_SIGNED_R11_EAC;
                header.glBaseInternalFormat = KTXHeader.GL_RED;
                bpp = 4;
                break;
               
            case EAC_RG:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_RG11_EAC;
                header.glBaseInternalFormat = KTXHeader.GL_RG;
                bpp = 8;
                break;
               
            case EAC_RG_SIGNED:
                header.glInternalFormat = KTXHeader.GL_COMPRESSED_SIGNED_RG11_EAC;
                header.glBaseInternalFormat = KTXHeader.GL_RG;
                bpp = 4;
                break;
               
            default:
                throw new IllegalStateException("Invalid texture format for KTX: " + tex.textureFormat);
        }
       
        // header + raw image data + mip map image sizes
        int imageSizeTotal = KTXHeader.SIZE + tex.imageBuffer.capacity() + header.numberOfMipmapLevels * 4;
        ByteBuffer bb = ByteBuffer.allocateDirect(imageSizeTotal);
       
        // write header
        header.write(DataOutputWriter.newWriter(bb));
       
        int mipMapWidth = header.pixelWidth;
        int mipMapHeight = header.pixelHeight;
        int mipMapOffset = 0;
        for (int i = 0; i < header.numberOfMipmapLevels; i++) {
            // write mip map size
            bb.putInt(mipMapWidth);
           
            // get mip map image data
            int mipMapSize = (mipMapWidth * mipMapHeight * bpp) / 8;
            ByteBuffer mipMapBuffer = ByteBufferUtils.getSlice(tex.imageBuffer, mipMapOffset, mipMapSize);

            // write image data
            bb.put(mipMapBuffer);
           
            // prepare next mip map
            mipMapWidth /= 2;
            mipMapHeight /= 2;
            mipMapOffset += mipMapSize;
        }

        // write file
        bb.rewind();

        setOutputFileName(tex.name);
        setOutputFileExtension("ktx");
        writeData(bb);
    }
   
}
TOP

Related Classes of info.ata4.unity.cli.extract.Texture2DHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.