Tool for compressing/decompressing ETC1 textures.

The ETC1 texture format is commonly supported by
OpenGL ES 2.0-capable GPUs.

For historical reasons ETC1 texture files have the
default extension .PKM

This tool relies on the libETC1 library to
compress and decompress the image data.
This commit is contained in:
Jack Palevich
2009-12-24 16:18:25 +08:00
parent e499caf961
commit c1645153e7
6 changed files with 828 additions and 17 deletions

View File

@@ -1405,7 +1405,17 @@
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name=".graphics.CompressedTextureActivity"
android:label="Graphics/OpenGL ES/Compressed Texture"
android:theme="@android:style/Theme.NoTitleBar"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name=".graphics.GLSurfaceViewActivity"
android:label="Graphics/OpenGL ES/GLSurfaceView"
android:theme="@android:style/Theme.NoTitleBar"

Binary file not shown.

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.apis.graphics;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.microedition.khronos.opengles.GL10;
import android.app.Activity;
import android.opengl.ETC1Util;
import android.opengl.GLES10;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import com.example.android.apis.R;
/**
* Demonstrate how to use ETC1 format compressed textures.
* This sample can be recompiled to use either resource-based
* textures (compressed offline using the etc1tool), or
* textures created on the fly by compressing images.
*
*/
public class CompressedTextureActivity extends Activity {
private final static String TAG = "CompressedTextureActivity";
/**
* Choose between creating a compressed texture on the fly or
* loading a compressed texture from a resource.
*/
private final static boolean TEST_CREATE_TEXTURE = false;
/**
* When creating a compressed texture on the fly, choose
* whether or not to use the i/o stream APIs.
*/
private final static boolean USE_STREAM_IO = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(this);
mGLView.setEGLConfigChooser(false);
StaticTriangleRenderer.TextureLoader loader;
if (TEST_CREATE_TEXTURE) {
loader = new SyntheticCompressedTextureLoader();
} else {
loader = new CompressedTextureLoader();
}
mGLView.setRenderer(new StaticTriangleRenderer(this, loader));
setContentView(mGLView);
}
@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}
@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}
/**
* Demonstrate how to load a compressed texture from an APK resource.
*
*/
private class CompressedTextureLoader implements StaticTriangleRenderer.TextureLoader {
public void load(GL10 gl) {
Log.w(TAG, "ETC1 texture support: " + ETC1Util.isETC1Supported());
InputStream input = getResources().openRawResource(R.raw.androids);
try {
ETC1Util.loadTexture(GLES10.GL_TEXTURE_2D, 0, 0,
GLES10.GL_RGB, GLES10.GL_UNSIGNED_SHORT_5_6_5, input);
} catch (IOException e) {
Log.w(TAG, "Could not load texture: " + e);
} finally {
try {
input.close();
} catch (IOException e) {
// ignore exception thrown from close.
}
}
}
}
/**
* Demonstrate how to create a compressed texture on the fly.
*/
private class SyntheticCompressedTextureLoader implements StaticTriangleRenderer.TextureLoader {
public void load(GL10 gl) {
int width = 128;
int height = 128;
Buffer image = createImage(width, height);
ETC1Util.ETC1Texture etc1Texture = ETC1Util.compressTexture(image, width, height, 3, 3 * width);
if (USE_STREAM_IO) {
// Test the ETC1Util APIs for reading and writing compressed textures to I/O streams.
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ETC1Util.writeTexture(etc1Texture, bos);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ETC1Util.loadTexture(GLES10.GL_TEXTURE_2D, 0, 0,
GLES10.GL_RGB, GLES10.GL_UNSIGNED_SHORT_5_6_5, bis);
} catch (IOException e) {
Log.w(TAG, "Could not load texture: " + e);
}
} else {
ETC1Util.loadTexture(GLES10.GL_TEXTURE_2D, 0, 0,
GLES10.GL_RGB, GLES10.GL_UNSIGNED_SHORT_5_6_5, etc1Texture);
}
}
private Buffer createImage(int width, int height) {
int stride = 3 * width;
ByteBuffer image = ByteBuffer.allocateDirect(height * stride)
.order(ByteOrder.nativeOrder());
// Fill with a pretty "munching squares" pattern:
for (int t = 0; t < height; t++) {
byte red = (byte)(255-2*t);
byte green = (byte)(2*t);
byte blue = 0;
for (int x = 0; x < width; x++) {
int y = x ^ t;
image.position(stride*y+x*3);
image.put(red);
image.put(green);
image.put(blue);
}
}
image.position(0);
return image;
}
}
private GLSurfaceView mGLView;
}

View File

@@ -50,9 +50,25 @@ import com.example.android.apis.R;
*/
public class StaticTriangleRenderer implements GLSurfaceView.Renderer{
public interface TextureLoader {
/**
* Load a texture into the currently bound OpenGL texture.
*/
void load(GL10 gl);
}
public StaticTriangleRenderer(Context context) {
init(context, new RobotTextureLoader());
}
public StaticTriangleRenderer(Context context, TextureLoader loader) {
init(context, loader);
}
private void init(Context context, TextureLoader loader) {
mContext = context;
mTriangle = new Triangle();
mTextureLoader = loader;
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
@@ -99,22 +115,7 @@ public class StaticTriangleRenderer implements GLSurfaceView.Renderer{
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
GL_REPLACE);
InputStream is = mContext.getResources()
.openRawResource(R.raw.robot);
Bitmap bitmap;
try {
bitmap = BitmapFactory.decodeStream(is);
} finally {
try {
is.close();
} catch(IOException e) {
// Ignore.
}
}
GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
mTextureLoader.load(gl);
}
public void onDrawFrame(GL10 gl) {
@@ -181,6 +182,27 @@ public class StaticTriangleRenderer implements GLSurfaceView.Renderer{
private Context mContext;
private Triangle mTriangle;
private int mTextureID;
private TextureLoader mTextureLoader;
private class RobotTextureLoader implements TextureLoader {
public void load(GL10 gl) {
InputStream is = mContext.getResources().openRawResource(
R.raw.robot);
Bitmap bitmap;
try {
bitmap = BitmapFactory.decodeStream(is);
} finally {
try {
is.close();
} catch (IOException e) {
// Ignore.
}
}
GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
}
}
static class Triangle {
public Triangle() {

40
tools/etc1tool/Android.mk Normal file
View File

@@ -0,0 +1,40 @@
# Copyright 2009 Google Inc. All Rights Reserved.
#
# Android.mk for etc1tool
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := etc1tool.cpp
LOCAL_C_INCLUDES += external/libpng
LOCAL_C_INCLUDES += external/zlib
LOCAL_C_INCLUDES += build/libs/host/include
LOCAL_C_INCLUDES += frameworks/base/opengl/include
#LOCAL_WHOLE_STATIC_LIBRARIES :=
LOCAL_STATIC_LIBRARIES := \
libhost \
libutils \
libcutils \
libexpat \
libpng \
libETC1
LOCAL_LDLIBS := -lz
ifeq ($(HOST_OS),linux)
LOCAL_LDLIBS += -lrt
endif
ifeq ($(HOST_OS),windows)
ifeq ($(strip $(USE_CYGWIN),),)
LOCAL_LDLIBS += -lws2_32
endif
endif
LOCAL_MODULE := etc1tool
include $(BUILD_HOST_EXECUTABLE)

582
tools/etc1tool/etc1tool.cpp Normal file
View File

@@ -0,0 +1,582 @@
// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <png.h>
#include <ETC1/etc1.h>
int writePNGFile(const char* pOutput, png_uint_32 width, png_uint_32 height,
const png_bytep pImageData, png_uint_32 imageStride);
const char* gpExeName;
static
void usage(char* message, ...) {
if (message) {
va_list ap;
va_start(ap, message);
vfprintf(stderr, message, ap);
va_end(ap);
fprintf(stderr, "\n\n");
fprintf(stderr, "usage:\n");
}
fprintf(
stderr,
"%s infile [--help | --encode | --encodeNoHeader | --decode] [--showDifference difffile] [-o outfile]\n",
gpExeName);
fprintf(stderr, "\tDefault is --encode\n");
fprintf(stderr, "\t\t--help print this usage information.\n");
fprintf(stderr,
"\t\t--encode create an ETC1 file from a PNG file.\n");
fprintf(
stderr,
"\t\t--encodeNoHeader create a raw ETC1 data file (without a header) from a PNG file.\n");
fprintf(stderr,
"\t\t--decode create a PNG file from an ETC1 file.\n");
fprintf(stderr,
"\t\t--showDifference difffile Write difference between original and encoded\n");
fprintf(stderr,
"\t\t image to difffile. (Only valid when encoding).\n");
fprintf(stderr,
"\tIf outfile is not specified, an outfile path is constructed from infile,\n");
fprintf(stderr, "\twith the apropriate suffix (.pkm or .png).\n");
exit(1);
}
// Returns non-zero if an error occured
static
int changeExtension(char* pPath, size_t pathCapacity, const char* pExtension) {
size_t pathLen = strlen(pPath);
size_t extensionLen = strlen(pExtension);
if (pathLen + extensionLen + 1 > pathCapacity) {
return -1;
}
// Check for '.' and '..'
if ((pathLen == 1 && pPath[0] == '.') || (pathLen == 2 && pPath[0] == '.'
&& pPath[1] == '.') || (pathLen >= 2 && pPath[pathLen - 2] == '/'
&& pPath[pathLen - 1] == '.') || (pathLen >= 3
&& pPath[pathLen - 3] == '/' && pPath[pathLen - 2] == '.'
&& pPath[pathLen - 1] == '.')) {
return -2;
}
int index;
for (index = pathLen - 1; index > 0; index--) {
char c = pPath[index];
if (c == '/') {
// No extension found. Append our extension.
strcpy(pPath + pathLen, pExtension);
return 0;
} else if (c == '.') {
strcpy(pPath + index, pExtension);
return 0;
}
}
// No extension or directory found. Append our extension
strcpy(pPath + pathLen, pExtension);
return 0;
}
void PNGAPI user_error_fn(png_structp png_ptr, png_const_charp message) {
fprintf(stderr, "PNG error: %s\n", message);
}
void PNGAPI user_warning_fn(png_structp png_ptr, png_const_charp message) {
fprintf(stderr, "PNG warning: %s\n", message);
}
// Return non-zero on error
int fwrite_big_endian_uint16(png_uint_32 data, FILE* pOut) {
if (fputc(0xff & (data >> 8), pOut) == EOF) {
return -1;
}
if (fputc(0xff & data, pOut) == EOF) {
return -1;
}
return 0;
}
// Return non-zero on error
int fread_big_endian_uint16(png_uint_32* data, FILE* pIn) {
int a, b;
if ((a = fgetc(pIn)) == EOF) {
return -1;
}
if ((b = fgetc(pIn)) == EOF) {
return -1;
}
*data = ((0xff & a) << 8) | (0xff & b);
return 0;
}
// Read a PNG file into a contiguous buffer.
// Returns non-zero if an error occurred.
// caller has to delete[] *ppImageData when done with the image.
int read_PNG_File(const char* pInput, etc1_byte** ppImageData,
etc1_uint32* pWidth, etc1_uint32* pHeight) {
FILE* pIn = NULL;
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
png_infop end_info = NULL;
png_bytep* row_pointers = NULL; // Does not need to be deallocated.
png_uint_32 width = 0;
png_uint_32 height = 0;
int result = -1;
etc1_byte* pSourceImage = 0;
if ((pIn = fopen(pInput, "rb")) == NULL) {
fprintf(stderr, "Could not open input file %s for reading: %d\n",
pInput, errno);
goto exit;
}
static const size_t PNG_HEADER_SIZE = 8;
png_byte pngHeader[PNG_HEADER_SIZE];
if (fread(pngHeader, 1, PNG_HEADER_SIZE, pIn) != PNG_HEADER_SIZE) {
fprintf(stderr, "Could not read PNG header from %s: %d\n", pInput,
errno);
goto exit;
}
if (png_sig_cmp(pngHeader, 0, PNG_HEADER_SIZE)) {
fprintf(stderr, "%s is not a PNG file.\n", pInput);
goto exit;
}
if (!(png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
(png_voidp) NULL, user_error_fn, user_warning_fn))) {
fprintf(stderr, "Could not initialize png read struct.\n");
goto exit;
}
if (!(info_ptr = png_create_info_struct(png_ptr))) {
fprintf(stderr, "Could not create info struct.\n");
goto exit;
}
if (!(end_info = png_create_info_struct(png_ptr))) {
fprintf(stderr, "Could not create end_info struct.\n");
goto exit;
}
if (setjmp(png_jmpbuf(png_ptr))) {
goto exit;
}
png_init_io(png_ptr, pIn);
png_set_sig_bytes(png_ptr, PNG_HEADER_SIZE);
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY
| PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_STRIP_ALPHA
| PNG_TRANSFORM_PACKING, NULL);
row_pointers = png_get_rows(png_ptr, info_ptr);
{
int bit_depth, color_type;
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
&color_type, NULL, NULL, NULL);
}
png_uint_32 stride = 3 * width;
pSourceImage = new etc1_byte[stride * height];
if (! pSourceImage) {
fprintf(stderr, "Out of memory.\n");
goto exit;
}
for (etc1_uint32 y = 0; y < height; y++) {
memcpy(pSourceImage + y * stride, row_pointers[y], stride);
}
*pWidth = width;
*pHeight = height;
*ppImageData = pSourceImage;
result = 0;
exit:
if (result) {
delete[] pSourceImage;
}
if (png_ptr) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
}
if (pIn) {
fclose(pIn);
}
return result;
}
// Read a PNG file into a contiguous buffer.
// Returns non-zero if an error occurred.
// caller has to delete[] *ppImageData when done with the image.
int readPKMFile(const char* pInput, etc1_byte** ppImageData,
etc1_uint32* pWidth, etc1_uint32* pHeight) {
int result = -1;
FILE* pIn = NULL;
etc1_byte header[ETC_PKM_HEADER_SIZE];
png_bytep pEncodedData = NULL;
png_bytep pImageData = NULL;
png_uint_32 width = 0;
png_uint_32 height = 0;
png_uint_32 stride = 0;
png_uint_32 encodedSize = 0;
if ((pIn = fopen(pInput, "rb")) == NULL) {
fprintf(stderr, "Could not open input file %s for reading: %d\n",
pInput, errno);
goto exit;
}
if (fread(header, sizeof(header), 1, pIn) != 1) {
fprintf(stderr, "Could not read header from input file %s: %d\n",
pInput, errno);
goto exit;
}
if (! etc1_pkm_is_valid(header)) {
fprintf(stderr, "Bad header PKM header for input file %s\n", pInput);
goto exit;
}
width = etc1_pkm_get_width(header);
height = etc1_pkm_get_height(header);
encodedSize = etc1_get_encoded_data_size(width, height);
pEncodedData = new png_byte[encodedSize];
if (!pEncodedData) {
fprintf(stderr, "Out of memory.\n");
goto exit;
}
if (fread(pEncodedData, encodedSize, 1, pIn) != 1) {
fprintf(stderr, "Could not read encoded data from input file %s: %d\n",
pInput, errno);
goto exit;
}
fclose(pIn);
pIn = NULL;
stride = width * 3;
pImageData = new png_byte[stride * height];
if (!pImageData) {
fprintf(stderr, "Out of memory.\n");
goto exit;
}
etc1_decode_image(pEncodedData, pImageData, width, height, 3, stride);
// Success
result = 0;
*ppImageData = pImageData;
pImageData = 0;
*pWidth = width;
*pHeight = height;
exit:
delete[] pEncodedData;
delete[] pImageData;
if (pIn) {
fclose(pIn);
}
return result;
}
// Encode the file.
// Returns non-zero if an error occurred.
int encode(const char* pInput, const char* pOutput, bool bEmitHeader, const char* pDiffFile) {
FILE* pOut = NULL;
etc1_uint32 width = 0;
etc1_uint32 height = 0;
etc1_uint32 encodedSize = 0;
int result = -1;
etc1_byte* pSourceImage = 0;
etc1_byte* pEncodedData = 0;
etc1_byte* pDiffImage = 0; // Used for differencing
if (read_PNG_File(pInput, &pSourceImage, &width, &height)) {
goto exit;
}
encodedSize = etc1_get_encoded_data_size(width, height);
pEncodedData = new etc1_byte[encodedSize];
if (!pEncodedData) {
fprintf(stderr, "Out of memory.\n");
goto exit;
}
etc1_encode_image(pSourceImage,
width, height, 3, width * 3, pEncodedData);
if ((pOut = fopen(pOutput, "wb")) == NULL) {
fprintf(stderr, "Could not open output file %s: %d\n", pOutput, errno);
goto exit;
}
if (bEmitHeader) {
etc1_byte header[ETC_PKM_HEADER_SIZE];
etc1_pkm_format_header(header, width, height);
if (fwrite(header, sizeof(header), 1, pOut) != 1) {
fprintf(stderr,
"Could not write header output file %s: %d\n",
pOutput, errno);
goto exit;
}
}
if (fwrite(pEncodedData, encodedSize, 1, pOut) != 1) {
fprintf(stderr,
"Could not write encoded data to output file %s: %d\n",
pOutput, errno);
goto exit;
}
fclose(pOut);
pOut = NULL;
if (pDiffFile) {
etc1_uint32 outWidth;
etc1_uint32 outHeight;
if (readPKMFile(pOutput, &pDiffImage, &outWidth, &outHeight)) {
goto exit;
}
if (outWidth != width || outHeight != height) {
fprintf(stderr, "Output file has incorrect bounds: %u, %u != %u, %u\n",
outWidth, outHeight, width, height);
goto exit;
}
const etc1_byte* pSrc = pSourceImage;
etc1_byte* pDest = pDiffImage;
etc1_uint32 size = width * height * 3;
for (etc1_uint32 i = 0; i < size; i++) {
int diff = *pSrc++ - *pDest;
diff *= diff;
diff <<= 3;
if (diff < 0) {
diff = 0;
} else if (diff > 255) {
diff = 255;
}
*pDest++ = (png_byte) diff;
}
writePNGFile(pDiffFile, outWidth, outHeight, pDiffImage, 3 * outWidth);
}
// Success
result = 0;
exit:
delete[] pSourceImage;
delete[] pEncodedData;
delete[] pDiffImage;
if (pOut) {
fclose(pOut);
}
return result;
}
int writePNGFile(const char* pOutput, png_uint_32 width, png_uint_32 height,
const png_bytep pImageData, png_uint_32 imageStride) {
int result = -1;
FILE* pOut = NULL;
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
if (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
(png_voidp) NULL, user_error_fn, user_warning_fn)) || !(info_ptr
= png_create_info_struct(png_ptr))) {
fprintf(stderr, "Could not initialize PNG library for writing.\n");
goto exit;
}
if (setjmp(png_jmpbuf(png_ptr))) {
goto exit;
}
if ((pOut = fopen(pOutput, "wb")) == NULL) {
fprintf(stderr, "Could not open output file %s: %d\n", pOutput, errno);
goto exit;
}
png_init_io(png_ptr, pOut);
png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr);
for (png_uint_32 y = 0; y < height; y++) {
png_write_row(png_ptr, pImageData + y * imageStride);
}
png_write_end(png_ptr, info_ptr);
result = 0;
exit: if (png_ptr) {
png_destroy_write_struct(&png_ptr, &info_ptr);
}
if (pOut) {
fclose(pOut);
}
return result;
}
int decode(const char* pInput, const char* pOutput) {
int result = -1;
png_bytep pImageData = NULL;
etc1_uint32 width = 0;
etc1_uint32 height = 0;
if (readPKMFile(pInput, &pImageData, &width, &height)) {
goto exit;
}
if (writePNGFile(pOutput, width, height, pImageData, width * 3)) {
goto exit;
}
// Success
result = 0;
exit: delete[] pImageData;
return result;
}
void multipleEncodeDecodeCheck(bool* pbEncodeDecodeSeen) {
if (*pbEncodeDecodeSeen) {
usage("At most one occurrence of --encode --encodeNoHeader or --decode is allowed.\n");
}
*pbEncodeDecodeSeen = true;
}
int main(int argc, char** argv) {
gpExeName = argv[0];
const char* pInput = NULL;
const char* pOutput = NULL;
const char* pDiffFile = NULL;
char* pOutputFileBuff = NULL;
bool bEncodeDecodeSeen = false;
bool bEncode = false;
bool bEncodeHeader = false;
bool bDecode = false;
bool bShowDifference = false;
for (int i = 1; i < argc; i++) {
const char* pArg = argv[i];
if (pArg[0] == '-') {
char c = pArg[1];
switch (c) {
case 'o':
if (pOutput != NULL) {
usage("Only one -o flag allowed.");
}
if (i + 1 >= argc) {
usage("Expected outfile after -o");
}
pOutput = argv[++i];
break;
case '-':
if (strcmp(pArg, "--encode") == 0) {
multipleEncodeDecodeCheck(&bEncodeDecodeSeen);
bEncode = true;
bEncodeHeader = true;
} else if (strcmp(pArg, "--encodeNoHeader") == 0) {
multipleEncodeDecodeCheck(&bEncodeDecodeSeen);
bEncode = true;
bEncodeHeader = false;
} else if (strcmp(pArg, "--decode") == 0) {
multipleEncodeDecodeCheck(&bEncodeDecodeSeen);
bDecode = true;
} else if (strcmp(pArg, "--showDifference") == 0) {
if (bShowDifference) {
usage("Only one --showDifference option allowed.\n");
}
bShowDifference = true;
if (i + 1 >= argc) {
usage("Expected difffile after --showDifference");
}
pDiffFile = argv[++i];
} else if (strcmp(pArg, "--help") == 0) {
usage( NULL);
} else {
usage("Unknown flag %s", pArg);
}
break;
default:
usage("Unknown flag %s", pArg);
break;
}
} else {
if (pInput != NULL) {
usage(
"Only one input file allowed. Already have %s, now see %s",
pInput, pArg);
}
pInput = pArg;
}
}
if (!bEncodeDecodeSeen) {
bEncode = true;
bEncodeHeader = true;
}
if ((! bEncode) && bShowDifference) {
usage("--showDifference is only valid when encoding.");
}
if (!pInput) {
usage("Expected an input file.");
}
if (!pOutput) {
const char* kDefaultExtension = bEncode ? ".pkm" : ".png";
size_t buffSize = strlen(pInput) + strlen(kDefaultExtension) + 1;
pOutputFileBuff = new char[buffSize];
strcpy(pOutputFileBuff, pInput);
if (changeExtension(pOutputFileBuff, buffSize, kDefaultExtension)) {
usage("Could not change extension of input file name: %s\n", pInput);
}
pOutput = pOutputFileBuff;
}
if (bEncode) {
encode(pInput, pOutput, bEncodeHeader, pDiffFile);
} else {
decode(pInput, pOutput);
}
delete[] pOutputFileBuff;
return 0;
}