nicastel / renderscript_texture_compressor

RenderscriptTextureCompressor
Apache License 2.0
4 stars 6 forks source link

[QUESTION] Write ETC1 Compressed Texture in a Swing Application #1

Open ndorigatti opened 10 years ago

ndorigatti commented 10 years ago

Hi @nicastel , I'm looking at this wonderful repo to find out if it could be possibile to write to a file the ETC1 compressed texture as pkm format instead of writing the PNGs.

I have done a swing application that from a GEOTIFF creates the tiles for worldwind but it writes PNG files. Since in the worldwind app, PNGs must be read, compressed and then showed, I'd prefer to compress the tiles in the desktop app since it is made one time only (the app will be offline, so tiles are prepared in desktop app). I am looking at the pure Java implementation but i see that in the file ETC1Benchmarck.testJavaETC1ImageCompressor you have a "testing purpose" file writing that states:

if (texture != null) { int estimatedMemorySize = ETC1.ETC_PKM_HEADER_SIZE+ texture.getHeight() * texture.getWidth() / 2; File f = new File(Environment.getExternalStorageDirectory(),"bmngpkm.pkm"); f.delete(); f.createNewFile(); ETC1Util.writeTexture(texture, new FileOutputStream(f)); System.out.println("Texture PKM created "); }

Do you think we can make ETC1Util.writeTexture available for Swing Application too? I was looking at what the method does http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.2_r1/android/opengl/ETC1Util.java#ETC1Util.writeTexture%28android.opengl.ETC1Util.ETC1Texture%2Cjava.io.OutputStream%29 and it seems to rely on "convertable" code, but two functions are native: ETC1.formatHeader and ETC1.getEncodedDataSize

from your expertees and knowledge, do you think is feasible or do you have already tried/done it?

The signature method of ETC1Util can be changed from: (ETC1Texture texture, OutputStream output) to (ByteBuffer dataBuffer, int width, int height, OutputStream output) and all the references to ETC1Texture on desktop are removed, but I'm scared about those native methods.

Many Thanks, Nicola

ndorigatti commented 10 years ago

I have just seen JavaETC1 file that have the utility function. Sorry for bothering you, my colleague will test it next days!!!

ndorigatti commented 10 years ago

I Have to reopen the issue since I have problems. I have a piece of code that creates BufferedImages with TRANSPARENT or OPAQUE style (either one or the other does not change), then i use your lib to do:

private ByteBuffer createByteBuf(BufferedImage image) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(image, "png", baos); baos.flush(); byte[] imageInByte = baos.toByteArray(); baos.close(); ByteBuffer buf = ByteBuffer.wrap(imageInByte); return buf; } catch (Exception e) {
System.out.println("Cannot create byte buffer from buffered image: " + e.getMessage()); } return null; }

private void writeToFilePkm(ByteBuffer buffer, int width, int height, String filePathName) throws FileNotFoundException, IOException {
    ByteBuffer compressedImage;
    final int encodedImageSize = JavaETC1.getEncodedDataSize(width, height);
    compressedImage = ByteBuffer.allocateDirect(encodedImageSize).order(ByteOrder.nativeOrder());
    JavaETC1.encodeImage(buffer, width, height,2, 2 * width, compressedImage);
    ETC1Texture texture = new ETC1Texture(width, height, compressedImage);

    buffer.rewind();
    if (compressedImage != null) {
        //int estimatedMemorySize = /* ETC1.ETC_PKM_HEADER_SIZE = 16 */16 + height * width / 2;
        File f = new File(filePathName + ".pkm");
        f.delete();
        f.createNewFile();
        JavaETC1Util.writeTexture(texture, new FileOutputStream(f));
        System.out.println("Texture PKM created ");
    }
}

When it goes to "encodeImage" it throws IndexOutOfBoundException.

There are some cases in which it creates pkm file but there is no rule, the PNGs are all maps printed on a canvas...

Can you help me on this?

nicastel commented 10 years ago

Well I never tested this function in JavaETC1 before. I put it in the code because it is essentially a pure Java port of the native ETC1 class / etc1.cpp from the android SDK so I wanted to keep the same level of functionality. I will try to reproduce the problem.

Thank you for raising this, it will be helpful in order to convert this code in a Geoserver plugin eventually.

ndorigatti commented 10 years ago

I think the problem is on png header parsed wrongly. I've found that there is a tool in android tools called "etc1tool" that converts PNG2PKM and back really fast and well! it is here: https://android.googlesource.com/platform/development/+/master/tools/etc1tool/etc1tool.cpp I see that the lib does something on PNG files and headers rather then directly use it in ETC1 utils or converting to android data. I don't have much knowledge on c++ to port it in less than months, so if you want and could, we can work together on this. I will look forward tomorrow to make a repo/testing pure java environment to test everything (i want to provide also the etc1tool.exe file, an example png and the example pkm converted from png using the tool).

2014-05-15 21:53 GMT+02:00 nicastel notifications@github.com:

Well I never tested this function in JavaETC1 before. I put it in the code because it is essentially a pure Java port of the native ETC1 class / etc1.cpp from the android SDK so I wanted to keep the same level of functionality. I will try to reproduce the problem.

Thank you for raising this, it will be helpful in order to convert this code in a Geoserver plugin eventually.

— Reply to this email directly or view it on GitHubhttps://github.com/nicastel/renderscript_texture_compressor/issues/1#issuecomment-43256990 .

ndorigatti commented 10 years ago

Nice progresses here! I've found a library that reads PNGs: http://hg.l33tlabs.org/twl/file/tip/src/de/matthiasmann/twl/utils/PNGDecoder.java

and using this library i made a correct conversion! I've checked with the official etc1tool and the decode then returns the correct image back! the only issue is that I don't think it will work for ARGB PNG's (these are the formats supported):

    ALPHA(1, true),
    LUMINANCE(1, false),
    LUMINANCE_ALPHA(2, true),
    RGB(3, false),
    RGBA(4, true),
    BGRA(4, true),
    ABGR(4, true);

FYI there is something that is not right in the encoding porting:

pIn.position(p);
pIn.get(block, q, xEnd * 3);
//System.arraycopy(pIn, p, block, q, xEnd * 3);

The arraycopy must be commented (strange but it doesn't work even if you keep only arraycopy).

This is what i've done:

    PNGDecoder2 decoder = new PNGDecoder2(input);
    System.out.println("width="+decoder.getWidth());
    System.out.println("height="+decoder.getHeight());

    ByteBuffer buf = ByteBuffer.allocateDirect(3*decoder.getWidth()*decoder.getHeight());
    decoder.decode(buf, decoder.getWidth()*3, Format.RGB);
    buf.flip();        
    writeToFilePkm(buf, decoder.getWidth(), decoder.getHeight(), "test.pkm");

and the function:

    private static void writeToFilePkm(ByteBuffer buffer, int width, int height, String filePathName) throws FileNotFoundException, IOException {
    ByteBuffer compressedImage;
    final int encodedImageSize = JavaETC1.getEncodedDataSize(width, height);
    compressedImage =  ByteBuffer.allocateDirect(encodedImageSize).order(ByteOrder.nativeOrder());
    JavaETC1.encodeImage(buffer, width, height,3, 3 * width, compressedImage);
    ETC1Texture texture = new ETC1Texture(width, height, compressedImage);

    buffer.rewind();
    if (compressedImage != null) {
        File f = new File(filePathName);
        f.delete();
        f.createNewFile();
        JavaETC1Util.writeTexture(texture, new FileOutputStream(f));            
    }
}

Hope it helps!

nicastel commented 10 years ago

Great! thank you for the feedback ant the testing done.

The compressor currently only support RGB888 or RGB565 raw pixels input. There is not support at all of RGBA input. It will be needed to adapt it.

To keep the alpha information the best will be to produce 2 ETC1 textures (one RGB and one A). I do not know if the PKM format can handle this nicely but the DDS and KTX formats can (via a texture array of 2 textures).

nicastel commented 10 years ago

You have here : https://code.google.com/p/pngj/ a pure java png decoder/encoder that is very fast. It is now use by geoserver as the default png encoder so I think that it should be good.

ndorigatti commented 10 years ago

Wow,thats nice! I will try the one you linked when I come back to office! I have an idea of alpha handling but I need some testing on it!

Sent from my Nexus 4 Il 16/mag/2014 22:29 "nicastel" notifications@github.com ha scritto:

You have here : https://code.google.com/p/pngj/ a pure java png decoder/encoder that is very fast. It is now use by geoserver as the default png encoder so I think that it should be good.

— Reply to this email directly or view it on GitHubhttps://github.com/nicastel/renderscript_texture_compressor/issues/1#issuecomment-43375862 .

ndorigatti commented 10 years ago

@nicastel having a brief look at the library, it seems good but seems to be not for direct image-bytebuffer translation but instead for png manipulation. We should check whether if it is faster of slower than the simple class linked some posts ago

nicastel commented 10 years ago

About this tool https://android.googlesource.com/platform/development/+/master/tools/etc1tool/etc1tool.cpp my JavaETC1 class is a direct port from the Android etc1.h/etc1.cpp C++ class that it is based on.

It will be very interesting for me to compare the performance of your pure java implementation and the performance of etc1tool on desktop Java (SUN JRE 7). I want to know if the poor performance of my implementation is linked to DalvikVM wich is known to be less efficient than SUN JVM or that it is really linked to the adaptation that I needed to done during the conversion (there is no unsigned type in java, it was not easy to make the thing works).

I have implemented a Geoserver plugin using JavaETC1 and the performance seems to be not so bad but I did not measure it formally yet.

ndorigatti commented 10 years ago

mm the etc1tool is c++ and not java, it is really fast (i'm converting 512x512 pngs). The implementation linked (pure java) seems to be quite fast, but can be improved looking at the pngj implementation i suppose...

Technocraft-Studio commented 3 years ago

I spent a couple hours for encoding image file such .png to etc1_rgb4 but nothing seem to work. I think the compressed source is headerless, its only image raw data.

C++ script i use for decoding is int decode_etc1(const uint8_t data, const long w, const long h, uint32_t image) { printbyte(data,8); long num_blocks_x = (w + 3) / 4; long num_blocks_y = (h + 3) / 4; uint32_t buffer[16]; for (long by = 0; by < num_blocks_y; by++) { for (long bx = 0; bx < num_blocks_x; bx++, data += 8) { decode_etc1_block(data, buffer); copy_block_buffer(bx, by, w, h, 4, 4, buffer, image); } } return 1; } void decode_etc1_block(const uint8_t data, uint32_t outbuf) { const uint_fast8_t code[2] = {data[3] >> 5, data[3] >> 2 & (uint8_t)7}; const uint_fast8_t *table = Etc1SubblockTable[data[3] & 1]; uint_fast8_t c[2][3]; if (data[3] & 2) { // diff bit == 1 c[0][0] = data[0] & 0xf8; c[0][1] = data[1] & 0xf8; c[0][2] = data[2] & 0xf8; c[1][0] = c[0][0] + (data[0] << 3 & 0x18) - (data[0] << 3 & 0x20); c[1][1] = c[0][1] + (data[1] << 3 & 0x18) - (data[1] << 3 & 0x20); c[1][2] = c[0][2] + (data[2] << 3 & 0x18) - (data[2] << 3 & 0x20); c[0][0] |= c[0][0] >> 5; c[0][1] |= c[0][1] >> 5; c[0][2] |= c[0][2] >> 5; c[1][0] |= c[1][0] >> 5; c[1][1] |= c[1][1] >> 5; c[1][2] |= c[1][2] >> 5; } else { // diff bit == 0 c[0][0] = (data[0] & 0xf0) | data[0] >> 4; c[1][0] = (data[0] & 0x0f) | data[0] << 4; c[0][1] = (data[1] & 0xf0) | data[1] >> 4; c[1][1] = (data[1] & 0x0f) | data[1] << 4; c[0][2] = (data[2] & 0xf0) | data[2] >> 4; c[1][2] = (data[2] & 0x0f) | data[2] << 4; }

uint_fast8_t j = data[6] << 8 | data[7];  
uint_fast8_t k = data[4] << 8 | data[5];  
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) {
    uint_fast8_t s = table[i];
    uint_fast8_t m = Etc1ModifierTable[code[s]][j & 1];
    outbuf[WriteOrderTable[i]] = applicate_color(c[s], k & 1 ? -m : m);
}

} Texture2D.zip

Sample ETC1_RGB4 compressed source and decoded image on attachment