2010-02-04

Image :: read/write TGA

public static BufferedImage readTGA(File file) throws IOException
   {
      if (!file.exists())
         throw new FileNotFoundException(file.getAbsolutePath());

      byte[] header = new byte[18];
      int len = (int) file.length() - header.length;
      if (len < 0)
         throw new IllegalStateException("file not big enough to contain header: " + file.getAbsolutePath());
      byte[] data = new byte[len];

      RandomAccessFile raf = new RandomAccessFile(file, "r");
      raf.read(header);
      raf.read(data);
      raf.close();

      if ((header[0] | header[1]) != 0)
         throw new IllegalStateException(file.getAbsolutePath());
      if (header[2] != 2)
         throw new IllegalStateException(file.getAbsolutePath());
      int w = 0, h = 0;
      w |= (header[12] & 0xFF) << 0;
      w |= (header[13] & 0xFF) << 8;
      h |= (header[14] & 0xFF) << 0;
      h |= (header[15] & 0xFF) << 8;

      boolean alpha;
      if ((w * h) * 3 == data.length)
         alpha = false;
      else if ((w * h) * 4 == data.length)
         alpha = true;
      else
         throw new IllegalStateException(file.getAbsolutePath());
      if (!alpha && (header[16] != 24))
         throw new IllegalStateException(file.getAbsolutePath());
      if (alpha && (header[16] != 32))
         throw new IllegalStateException(file.getAbsolutePath());
      if ((header[17] & 15) != (alpha ? 8 : 0))
         throw new IllegalStateException(file.getAbsolutePath());

      BufferedImage dst = new BufferedImage(w, h, alpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
      int[] pixels = ((DataBufferInt) dst.getRaster().getDataBuffer()).getData();
      if (pixels.length != w * h)
         throw new IllegalStateException(file.getAbsolutePath());
      if (data.length != pixels.length * (alpha ? 4 : 3))
         throw new IllegalStateException(file.getAbsolutePath());

      if (alpha)
      {
         for (int i = 0, p = (pixels.length - 1) * 4; i < pixels.length; i++, p -= 4)
         {
            pixels[i] |= ((data[p + 0]) & 0xFF) << 0;
            pixels[i] |= ((data[p + 1]) & 0xFF) << 8;
            pixels[i] |= ((data[p + 2]) & 0xFF) << 16;
            pixels[i] |= ((data[p + 3]) & 0xFF) << 24;
         }
      }
      else
      {
         for (int i = 0, p = (pixels.length - 1) * 3; i < pixels.length; i++, p -= 3)
         {
            pixels[i] |= ((data[p + 0]) & 0xFF) << 0;
            pixels[i] |= ((data[p + 1]) & 0xFF) << 8;
            pixels[i] |= ((data[p + 2]) & 0xFF) << 16;
         }
      }

      if ((header[17] >> 4) == 1)
      {
         // ok
      }
      else if ((header[17] >> 4) == 0)
      {
         // flip horizontally

         for (int y = 0; y < h; y++)
         {
            int w2 = w / 2;
            for (int x = 0; x < w2; x++)
            {
               int a = (y * w) + x;
               int b = (y * w) + (w - 1 - x);
               int t = pixels[a];
               pixels[a] = pixels[b];
               pixels[b] = t;
            }
         }
      }
      else
      {
         throw new UnsupportedOperationException(file.getAbsolutePath());
      }

      return dst;
   }

   public static void writeTGA(BufferedImage src, File file) throws IOException
   {
      DataBuffer buffer = src.getRaster().getDataBuffer();
      boolean alpha = src.getColorModel().hasAlpha();
      byte[] data;

      if (buffer instanceof DataBufferByte)
      {
         byte[] pixels = ((DataBufferByte) src.getRaster().getDataBuffer()).getData();
         if (pixels.length != src.getWidth() * src.getHeight() * (alpha ? 4 : 3))
            throw new IllegalStateException();

         data = new byte[pixels.length];

         for (int i = 0, p = pixels.length - 1; i < data.length; i++, p--)
         {
            data[i] = pixels[p];
         }
      }
      else if (buffer instanceof DataBufferInt)
      {
         int[] pixels = ((DataBufferInt) src.getRaster().getDataBuffer()).getData();
         if (pixels.length != src.getWidth() * src.getHeight())
            throw new IllegalStateException();

         data = new byte[pixels.length * (alpha ? 4 : 3)];

         if (alpha)
         {
            for (int i = 0, p = pixels.length - 1; i < data.length; i += 4, p--)
            {
               data[i + 0] = (byte) ((pixels[p] >> 0) & 0xFF);
               data[i + 1] = (byte) ((pixels[p] >> 8) & 0xFF);
               data[i + 2] = (byte) ((pixels[p] >> 16) & 0xFF);
               data[i + 3] = (byte) ((pixels[p] >> 24) & 0xFF);
            }
         }
         else
         {
            for (int i = 0, p = pixels.length - 1; i < data.length; i += 3, p--)
            {
               data[i + 0] = (byte) ((pixels[p] >> 0) & 0xFF);
               data[i + 1] = (byte) ((pixels[p] >> 8) & 0xFF);
               data[i + 2] = (byte) ((pixels[p] >> 16) & 0xFF);
            }
         }
      }
      else
      {
         throw new UnsupportedOperationException();
      }

      byte[] header = new byte[18];
      header[2] = 2; // uncompressed, true-color image
      header[12] = (byte) ((src.getWidth() >> 0) & 0xFF);
      header[13] = (byte) ((src.getWidth() >> 8) & 0xFF);
      header[14] = (byte) ((src.getHeight() >> 0) & 0xFF);
      header[15] = (byte) ((src.getHeight() >> 8) & 0xFF);
      header[16] = (byte) (alpha ? 32 : 24); // bits per pixel
      header[17] = (byte) ((alpha ? 8 : 0) | (1 << 4));

      RandomAccessFile raf = new RandomAccessFile(file, "rw");
      raf.write(header);
      raf.write(data);
      raf.setLength(raf.getFilePointer()); // trim
      raf.close();
   }

5 comments:

  1. Hi. I would like to include this code (writeTGA) into my project http://perceptron.sourceforge.net/
    I am using a very slow method of saving images, and this one works a lot better. Is it okay to attribute you in the source code like this:

    /**
    * Read and write TGA image files.
    *
    * @author Riven
    * http://riven8192.blogspot.com/2010/02/image-readwrite-tga.html
    *
    */

    ReplyDelete
  2. I am very new for the TGA images, Your code helped lot to create a TGA file. But writeTGA output look like flipped horizontally, is there any idea to fix this?

    ReplyDelete
  3. Horizontal flipping is controlled by the 5th bit of the 17th byte of the header.

    header[17] = (byte) ((alpha ? 8 : 0) | (1 << 4));
    header[17] = (byte) ((alpha ? 8 : 0) | (0 << 4));

    ReplyDelete