2011/12/04

My Gimp Magic

I'm a Linux user (distribution-agnostic) so I have to bolt my seat down in the airplane but once done, it is really comfortable. The same thing applies to Gimp. Today I spent some time with it on a fresh installation and found out all the good plugins I ever used. The important point here is that there are only three of them to do what I really need. And, of course, all of this can be done manually in a clean Gimp installation but it might take ages.

Contrast Blending aka HDR Photography

High dynamic range imaging is a set of techniques that allow displaying images with big differences between dark and light parts on a limited device. A limited device is a usual computer screen or a printed image. None of these media can fully display the range that a human eye is able to see. So we need to make a compromise in the scene.

There is a great Gimp plugin and tutorial for HDR blenging. The basic principle is to take three pictures with different exposure values (dark, normal and bright) and mix them together. A good algorithm can do that pretty easily for you. A sample picture can be seen below. In the left upper corner, there is the dark exposure. In the upper right corner, there is the normal exposure, the bright exposure is placed at lower left, and, eventually, the result image is at lower right. Note that you can see the background, while there is the candle wick still visible below the light.

HDR Photo

Layer Effects

In Photoshop, layer effects are built-in. These effects can do some easy manipulation with the image and is one the most criticised lacks of Gimp. Fortunately, there is a plugin that can be easily installed and works pretty much the same way.

Layer Effects Demo

Shadows and Highlights

This plugin allows you to manipulate dark and light parts of the scene. It can automatically extract these parts into new layers and by using the layer opacity you can configure the light in shadows and darken the bright parts.

This is not such a miracle, but it is very useful in infrared (IR) photography.

IR Photography

The biggest issue with IR photography is that usual DSLR cameras have a fixed built-in IR filter before the image chip. This is to avoid unwanted light. But for IR photos we want it! The solution (except for camera disassembly or buying Sigma SD14 with removable filter) is to increase exposure period. It can take several minutes to get enough light with an IR filter on your lens.

Now, when we have a photo, how to process the red scene we obtained? There is a great Photoshop IR tutorial. But I wanted to do that in Gimp. There is a tutorial in a forum that resembles the Photoshop procedure. To summarize that:

  1. Realize what is in individual color channels - red is brightness, green is sharpness, blue is noise.
  2. In Gimp go to Colors, Levels and try Automatic or set a reference white point (which is something that produces most IR light - a green leaf on a sun for instance). You can also setup individual channels as you like.
  3. In Gimp select Colors, Components, Channel mixer and switch the red and the blue channels. This is done by setting 100% for blue and 0% for red with the red input channel and vice versa for the blue input channel.
  4. Now is the best time for the Shadows and Highlights plugin.
  5. Finally, you can try Colors, Hue/Saturation and add a little bit of Hue (+15 - +24) and possibly add some Saturation as well.
  6. Bonus step is to open Colors, Curves or Colors, Levels and fine tune the image colors.
IR Photo

2011/05/17

Accessing ODBC from Java

Connecting to an ODBC database from Java can be a real nightmare. Most solutions suggest using a JDBC-ODBC bridge. So what are real possibilities here? Native JDBC-ODBC bridge by Sun/Oracle sun.jdbc.odbc.JdbcOdbcDriver. Well, there are some limitations like the number of concurrent connections and poor performance. I was not able to find any free (ideally open source) JDBC-ODBC bridge. There are multiple vendors but only one seems to have a de facto standard - Easysoft. They provide trial version of their bridge so you can try the solution before you buy it. Here we realized some troubles in Java Virtual Machine consuming too much memory. I do not want to blame the bridge. It was on a separate machine but we are not able to debug it and see how it works. So not being opensource killed Easysoft's chance to get a new customer.

After all I decided to write a small Java client that would be able to access an ODBC database. We needed just some basic querying capabilities anyway. But this is a little tricky, there is no ODBC communication protocol specification. Eventually, I decided to use unixODBC library using JNI. And here is beginning of the story.

First I designed a client for accessing the database. I had some previous ODBC API knowledge so I designed the client in order to write minimum code in C.

A design note: I failed passing database handles (type void*) between C and Java, so I created an array to store real handles in the C library and pass just an index to the array. This requires tha Java part to remember which handles have been used. There definitely exist a more sophisticated solution but I wanted to query an ODBC database at first. Also note that all data are returned as Strings for simplicity. There is also a shor main() method to test the client.

package org.marvec.odbc;

public class OdbcClient {
   
   private static SortedSet usedHandles = new TreeSet();
   private int handle;
   
   public native void connect(String connection) throws IOException;
   public native void execute(String statement) throws IOException;
   public native int getNumCols();
   public native ColumnMetadata getColMetadata(int col);
   public native boolean fetch();
   public native String getColData(int col) throws IOException;
   public native void freeStatement();
   private native void close();
      
   public OdbcClient() throws IOException {
      int i = 0;
      while (usedHandles.contains(i) && i <= 1024) {
         i++;
      }
      if (i == 1024) {
         throw new IOException("All handles are currently in use. Try to free some handles by disconnecting from ODBC.");
      }
      usedHandles.add(i);
      handle = i;
   }

   public void disconnect() {
      close();
      usedHandles.remove(handle);
   }

   public static class ColumnMetadata {
       public String name;
       public int type;
       public long length;
       public int digits;
       this.nullable = nullable;

       public String toString() {
          return name + ": " + type + "(" + length + ")" + "[" + digits + "]" + (nullable ? "*" : "");
       }
   }

   public static void main(String... args) throws Exception {
       System.loadLibrary(args[0]);
       OdbcClient c = new OdbcClient();
       c.connect("DRIVER={PostgreSQL64};DATABASE=myodbcdb;SERVER=localhost;PORT=35432;Uid=admin;Pwd=admin;");
       c.execute("select * from persons where person_id < 1000");
       System.out.println("Columns: " + c.getNumCols());
       for (int i = 1, j = c.getNumCols(); i <= j; i++) {
           ColumnMetadata meta = c.getColMetadata(i);
           System.out.println("Column " + i + ": " + meta);
       }
       while (c.fetch()) {
           for (int i = 1, j = c.getNumCols(); i <= j; i++) {
               System.out.print(c.getColData(i) + (i == j ? "\n" : ", "));
           }
       }
       c.freeStatement();
       c.disconnect();
   }
}

After compilation, I neede javah utility to obtain a C header file (.h).

javah -jni org.marvec.odbc.OdbcClient

Now the hard C part begins. I'm not a C guru and my compiler reports many warnings about my code. However, it compiles and works. First I copied org_marvec_odbc_OdbcClient.h to org_marvec_odbc_OdbcClient.c and added the shared handle storage to the header file as well as necessary includes. As you can see, we need to remember three different handles for each connection.

#include 
#include 
...
SQLHENV env[1024];
SQLHDBC dbc[1024];
SQLHSTMT stmt[1024];

You might find me wasting some memory but you know, in Java world, there is always enough memory... I will show you only some parts of the solution, link to a complete package is at the end of this post. The most difficult function is getting column metadata that requires creating a new Java object from C.

JNIEXPORT jobject JNICALL Java_org_marvec_odbc_OdbcClient_getColMetadata(JNIEnv *jnienv, jobject obj, jint col) {
    int h = getStaticHandle(jnienv, obj);
    SQLSMALLINT nameLength, dataType, decimalDigits, nullable;
    SQLULEN colSize;
    jstring jstr;
    char name[512];
    
    SQLDescribeCol(stmt[h], (SQLUSMALLINT) col, name, sizeof(name), &nameLength, &dataType, &colSize, &decimalDigits, &nullable);

    jstr = (*jnienv)->NewStringUTF(jnienv, name);

    jclass clazz;
    jmethodID cid;
    jobject meta;

    clazz = (*jnienv)->FindClass(jnienv, "org/marvec/odbc/OdbcClient$ColumnMetadata");
    cid = (*jnienv)->GetMethodID(jnienv, clazz, "", "(Ljava/lang/String;IJIZ)V");
    meta = (*jnienv)->NewObject(jnienv, clazz, cid, jstr, dataType, colSize, decimalDigits, (jboolean) (nullable == SQL_NULLABLE));

    return meta;
}

In some problematic methods I also check for errors using another utility library I mostly copied from Jinput project (see function throwIOException() for example).

JNIEXPORT void JNICALL Java_org_marvec_odbc_OdbcClient_connect(JNIEnv *jnienv, jobject obj, jstring conn) {
    SQLRETURN ret;
    jbyte *str;
    int h = getStaticHandle(jnienv, obj);
    char error[10240];

    jclass cls = (*jnienv)->GetObjectClass(jnienv, obj);

    str = (*jnienv)->GetStringUTFChars(jnienv, conn, NULL);

    SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &(env[h]));
    SQLSetEnvAttr(env[h], SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
    SQLAllocHandle(SQL_HANDLE_DBC, env[h], &(dbc[h]));
    ret = SQLDriverConnect(dbc[h], NULL, str, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);

    if (!SQL_SUCCEEDED(ret)) {
        extractError(dbc[h], SQL_HANDLE_DBC, error, sizeof(error));
        throwIOException(jnienv, "SQLDriverConnect failed with return value %d:\n%s", ret, error);
    }

    (*jnienv)->ReleaseStringUTFChars(jnienv, conn, str);
}

Now there are some dependencies in the C project. You must have installed the following libraries in your system:

  • unixODBC
  • unixODBC-dev
  • some ODBC driver for your target database like odbc-postgresql
  • libltdl
  • and some common C libraries that are likely to be already present on your computer: pthread, dl

Now you must configure ODBC to provide you the driver (see the first part of the connection string DRIVER=). This is done in /etc/odbcinst.ini and we can configure both 32- and 64-bit versions:

[PostgreSQL]
Description     = PostgreSQL driver for Linux & Win32
Driver          = /usr/lib/odbc/psqlodbca.so
Setup           = /usr/lib/odbc/libodbcpsqlS.so

[PostgreSQL64]
Description     = PostgreSQL driver for Linux & Win32
Driver          = /usr/lib64/odbc/psqlodbca.so
Setup           = /usr/lib64/odbc/libodbcpsqlS.so

To compile the C library I created a small Makefile:

CC=gcc
CFLAGS=-shared -fPIC -w -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux
PROJECT=org_marvec_odbc_OdbcClient

all: $(PROJECT).so

$(PROJECT).so: $(PROJECT).c
        $(CC) $(CFLAGS) util.c $(PROJECT).c -o $(PROJECT).so -lodbc -lpthread -lltdl -ldl
        ln -sf $(PROJECT).so libjavaODBC.so

clean:
        rm $(PROJECT).so libjavaODBC.so

Note the creation of a symbolic link. Java loads libraries on library path with standard name lib[name fo the library].so and only the library name is what you pass as an argument to System.loadLibrary().

Now to run the client just call:

java -Djava.library.path=. org.marvec.odbc.OdbcClient javaODBC

Feel free to download the complete source code at github. The code is provided as is, I do not take any responsibility for any damage it might cause to anybody or anything (including hardware, software, people, animals, etc). For instructions see JBoss Community document.

. .