TGA files randomly flips?

Im saving some tga files, but it sadly when loading sometimes flips the image…

layers[currentlayer].save(directory[0]+"/undo/frame_"+currentlayer+"_"+undoimgids[currentlayer]+".tga");

this is used to save the file!

undoimg = loadImage(directory[0]+"/undo/frame_"+currentlayer+"_"+undoimgids[currentlayer]+".tga");

this loads it!

1 Like

Hi @Aryszin,

Can you elaborate on that? Does it flips the image on a specific axis? (x or y)

Can you provide an example .tga file that does the flip “sometimes”?

It flips in the y axis when loading.

Hello @Aryszin,

My exploration of this…

There is a GitHub issue here:

Source code here:

Give it a moment to find the line when opening!

TGA Specifications here:

I suggest you download it to view.

I modified the source code and providing an example to “reverse” the image on loading (toggle between two choices with mouse press):

// Loading a TGA and selecting how to "reverse" image (2 choices)
// glv
// 2023-03-23
// Source code was was used on example.

//https://github.com/processing/processing/issues/1682
//https://github.com/DEAKSoftware/Truevision-TGA/blob/master/truevision-tga.pdf
//https://github.com/processing/processing4/blob/0c02c66f9e2fdf2fa13d796a473f4acc35154517/core/src/processing/core/PImage.java

PImage img0, img1, img2;
boolean toggle = true;
int org = 0;
String url;

void setup()
  {
  size(400, 200);

  url = "http://learningprocessing.com/code/assets/sunflower.jpg"; //This can also be your image
  img0 = loadImage(url);
  
  // Save image with Processing  
  img0.save("/data/test.tga");
  
  textSize(24);
  textAlign(CENTER, CENTER);
  
    toggle = !toggle;
  
  // try\catch because myloadTGA() throws exception
  try {
      InputStream input = createInput("/data/test.tga");
      img1 = myloadTGA(input); 
      } 
  catch(IOException e) 
      {
      System.out.println("!");
      } 

  noLoop();
  }
  
void draw()
  {
  
  image(img0, 0, 0);
  text("BEFORE", width/4, 10) ;
  
  image(img1, 200, 0);
  text("AFTER", 3*width/4, 10) ;
  }
  
void mousePressed()
  {  
  toggle = !toggle;
  
  // try\catch because myloadTGA() throws exception
  try {
      InputStream input = createInput("/data/test.tga");
      img1 = myloadTGA(input); 
      } 
  catch(IOException e) 
      {
      System.out.println("!");
      }   
  
  redraw();  
  }

//******************************************************************

// Source is:
//https://github.com/processing/processing4/blob/0c02c66f9e2fdf2fa13d796a473f4acc35154517/core/src/processing/core/PImage.java#L2806
// see //glv comments for changes

/**
 * Targa image loader for RLE-compressed TGA files.
 * <p>
 * Rewritten for 0115 to read/write RLE-encoded targa images.
 * For 0125, non-RLE encoded images are now supported, along with
 * images whose y-order is reversed (which is standard for TGA files).
 * <p>
 * A version of this function is in MovieMaker.java. Any fixes here
 * should be applied over in MovieMaker as well.
 * <p>
 * Known issue with RLE encoding and odd behavior in some apps:
 * https://github.com/processing/processing/issues/2096
 * Please help!
 */
PImage myloadTGA(InputStream input) throws IOException {  // ignore
  byte[] header = new byte[18];
  int offset = 0;
  do {
    int count = input.read(header, offset, header.length - offset);
    if (count == -1) return null;
    offset += count;
  } while (offset < 18);

  /*
      header[2] image type code
   2  (0x02) - Uncompressed, RGB images.
   3  (0x03) - Uncompressed, black and white images.
   10 (0x0A) - Run-length encoded RGB images.
   11 (0x0B) - Compressed, black and white images. (grayscale?)
   header[16] is the bit depth (8, 24, 32)
   header[17] image descriptor (packed bits)
   0x20 is 32 = origin upper-left
   0x28 is 32 + 8 = origin upper-left + 32 bits
   7  6  5  4  3  2  1  0
   128 64 32 16  8  4  2  1
   */

  int format = 0;

  if (((header[2] == 3) || (header[2] == 11)) &&  // B&W, plus RLE or not
    (header[16] == 8) &&  // 8 bits
    ((header[17] == 0x8) || (header[17] == 0x28))) {  // origin, 32 bit
    format = ALPHA;
  } else if (((header[2] == 2) || (header[2] == 10)) &&  // RGB, RLE or not
    (header[16] == 24) &&  // 24 bits
    ((header[17] == 0x20) || (header[17] == 0))) {  // origin
    format = RGB;
  } else if (((header[2] == 2) || (header[2] == 10)) &&
    (header[16] == 32) &&
    ((header[17] == 0x8) || (header[17] == 0x28))) {  // origin, 32
    format = ARGB;
  }

  if (format == 0) {
    System.err.println("Unknown .tga file format");
    return null;
  }

  int w = ((header[13] & 0xff) << 8) + (header[12] & 0xff);
  int h = ((header[15] & 0xff) << 8) + (header[14] & 0xff);
  PImage outgoing = new PImage(w, h, format);

//********************************* glv edit *********************************

  // where "reversed" means upper-left corner (normal for most of
  // the modernized world, but "reversed" for the tga spec)
  // https://github.com/processing/processing/issues/1682
  
  boolean reversed = false;
  if(toggle == true)
    reversed = (header[17] & 0x20) != 0;
  else if(toggle == false)
    reversed = (header[17] & 0x20) == 0;
  println("header[17] =", hex(header[17] & 0x20, 2), reversed); 
   
//****************************************************************************

  if ((header[2] == 2) || (header[2] == 3)) {  // not RLE encoded
    if (reversed) {
      int index = (h-1) * w;
      switch (format) {
      case ALPHA:
        for (int y = h-1; y >= 0; y--) {
          for (int x = 0; x < w; x++) {
            outgoing.pixels[index + x] = input.read();
          }
          index -= w;
        }
        break;
      case RGB:
        for (int y = h-1; y >= 0; y--) {
          for (int x = 0; x < w; x++) {
            outgoing.pixels[index + x] =
              input.read() | (input.read() << 8) | (input.read() << 16) |
              0xff000000;
          }
          index -= w;
        }
        break;
      case ARGB:
        for (int y = h-1; y >= 0; y--) {
          for (int x = 0; x < w; x++) {
            outgoing.pixels[index + x] =
              input.read() | (input.read() << 8) | (input.read() << 16) |
              (input.read() << 24);
          }
          index -= w;
        }
      }
    } else {  // not reversed
      int count = w * h;
      switch (format) {
      case ALPHA:
        for (int i = 0; i < count; i++) {
          outgoing.pixels[i] = input.read();
        }
        break;
      case RGB:
        for (int i = 0; i < count; i++) {
          outgoing.pixels[i] =
            input.read() | (input.read() << 8) | (input.read() << 16) |
            0xff000000;
        }
        break;
      case ARGB:
        for (int i = 0; i < count; i++) {
          outgoing.pixels[i] =
            input.read() | (input.read() << 8) | (input.read() << 16) |
            (input.read() << 24);
        }
        break;
      }
    }
  } else {  // header[2] is 10 or 11
    int index = 0;
    int[] px = outgoing.pixels;

    while (index < px.length) {
      int num = input.read();
      boolean isRLE = (num & 0x80) != 0;
      if (isRLE) {
        num -= 127;  // (num & 0x7F) + 1

//********************************* glv edit *********************************
// glv This was giving me issues with errors; I suspect the IDE and NOT the code 

        //int pixel = 0;
        //pixel = switch (format)
        //  {
        //  case ALPHA -> input.read();
        //  case RGB -> 0xFF000000 |
        //    input.read() | (input.read() << 8) | (input.read() << 16);
        //  case ARGB -> input.read() |
        //    (input.read() << 8) | (input.read() << 16) | (input.read() << 24);
        //  default -> 0;
        //  };

     // glv This worked! Borrowed form Processing 3 source
        int pixel = 0;
        switch (format) {
        case ALPHA:
          pixel = input.read();
          break;
        case RGB:
          pixel = 0xFF000000 |
            input.read() | (input.read() << 8) | (input.read() << 16);
          //(input.read() << 16) | (input.read() << 8) | input.read();
          break;
        case ARGB:
          pixel = input.read() |
            (input.read() << 8) | (input.read() << 16) | (input.read() << 24);
          break;
        }

//****************************************************************************

        for (int i = 0; i < num; i++) {
          px[index++] = pixel;
          if (index == px.length) break;
        }
      } else {  // write up to 127 bytes as uncompressed
        num += 1;
        switch (format) {
        case ALPHA:
          for (int i = 0; i < num; i++) {
            px[index++] = input.read();
          }
          break;
        case RGB:
          for (int i = 0; i < num; i++) {
            px[index++] = 0xFF000000 |
              input.read() | (input.read() << 8) | (input.read() << 16);
            //(input.read() << 16) | (input.read() << 8) | input.read();
          }
          break;
        case ARGB:
          for (int i = 0; i < num; i++) {
            px[index++] = input.read() | //(input.read() << 24) |
              (input.read() << 8) | (input.read() << 16) | (input.read() << 24);
            //(input.read() << 16) | (input.read() << 8) | input.read();
          }
          break;
        }
      }
    }

    if (!reversed) {
      int[] temp = new int[w];
      for (int y = 0; y < h/2; y++) {
        int z = (h-1) - y;
        System.arraycopy(px, y*w, temp, 0, w);
        System.arraycopy(px, z*w, px, y*w, w);
        System.arraycopy(temp, 0, px, z*w, w);
      }
    }
  }
  input.close();
  return outgoing;
}

image

image

This can easily be modified to make a custom method for your needs.

:)

1 Like