0

Sample code based on this [posted](Storing message into R,G,B instead of Alpha

This time i would like to ONLY RGB instead of ARGB, but this time i received a byte length is 2147483647. Below are the part of the code where i changed.

A input is only 128 bytes array.

EmbedMessage

private void openImage() {
    File f = new File("C:/TEMP/CROP.png");

        try {
            sourceImage = ImageIO.read(f); 


             sourceImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_RGB); 
             Graphics2D g = sourceImage.createGraphics(); 
             g.drawImage(ImageIO.read(f), 0, 0, null); 
             g.dispose();

            this.embedMessage();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

private void embedInteger(BufferedImage img, int n, int start) {
    int maxX = img.getWidth(), maxY = img.getHeight(), 
            startX = start/maxY, startY = start - startX*maxY, count=0;

    for(int i=startX; i<maxX && count<32; i++) {
        for(int j=startY; j<maxY && count<32; j++) {
             int rgb = img.getRGB(i, j);
            // bit = getBitValue(n, count);
             //rgb = setBitValue(rgb, 0, bit);
         int bit = getBitValue(n, count); rgb = setBitValue(rgb, 0, bit);
             bit = getBitValue(n, count+1); rgb = setBitValue(rgb, 8, bit);
             bit = getBitValue(n, count+2); rgb = setBitValue(rgb, 16, bit);
             img.setRGB(i, j, rgb); 
             count = count+3;

        }
    }
}

private void embedByte(BufferedImage img, byte b, int start) {
    int maxX = img.getWidth(), maxY = img.getHeight(), 
            startX = start/maxY, startY = start - startX*maxY, count=0;

    for(int i=startX; i<maxX && count<8; i++) {
        for(int j=startY; j<maxY && count<8; j++) {
            if(j==maxY-1) {
                   startY = 0;
                }
             int rgb = img.getRGB(i, j);
             //bit = getBitValue(b, count);
            // rgb = setBitValue(rgb, 0, bit);
         int bit = getBitValue(b, count); rgb = setBitValue(rgb, 0, bit);
             bit = getBitValue(b, count+1); rgb = setBitValue(rgb, 8, bit);
             bit = getBitValue(b, count+2); rgb = setBitValue(rgb, 16, bit);
             img.setRGB(i, j, rgb);
             count = count+3;
        }
    }
}

DecodeMessage

private void openImage() throws Exception {
    File f = new File("C:/TEMP/Four Area/Crop image/chest-CROP2.png");
    //File f = new File("C:/TEMP/chest2.png");

        try {
            image = ImageIO.read(f); 

            image = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); 
             Graphics2D g = image.createGraphics(); 
             g.drawImage(ImageIO.read(f), 0, 0, null); 
             g.dispose();

            this.decodeMessage();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


}


private int extractInteger(BufferedImage img, int start) {
    int maxX = img.getWidth(), maxY = img.getHeight(), 
            startX = start/maxY, startY = start - startX*maxY, count=0;

    int length = 0;
    for(int i=startX; i<maxX && count<32; i++) {
        for(int j=startY; j<maxY && count<32; j++) {

             int rgb = img.getRGB(i, j); 
             //bit = getBitValue(rgb, 0);
             //length = setBitValue(length, count, bit);
         int bit = getBitValue(rgb, 0); length = setBitValue(length, count, bit);
             bit = getBitValue(rgb, 8); length = setBitValue(length, count+1, bit);
             bit = getBitValue(rgb, 16); length = setBitValue(length, count+2, bit);
             count = count+3;

        }
    }
    return length;

}


private byte extractByte(BufferedImage img, int start) {
    int maxX = img.getWidth(), maxY = img.getHeight(), 
            startX = start/maxY, startY = start - startX*maxY, count=0;

    byte b = 0;
    for(int i=startX; i<maxX && count<8; i++) {
        for(int j=startY; j<maxY && count<8; j++) {
            if(j==maxY-1) {
                   startY = 0;
                }
             int rgb = img.getRGB(i, j); 

             //bit = getBitValue(rgb, 0);
             //b = (byte)setBitValue(b, count, bit);
           int bit = getBitValue(rgb, 0); b = (byte)setBitValue(b, count, bit);
               bit = getBitValue(rgb, 8); b = (byte)setBitValue(b, count+1, bit);
               bit = getBitValue(rgb, 16); b = (byte)setBitValue(b, count+2, bit);
             count = count+3;
        }
    }
    return b;
}
Community
  • 1
  • 1
Jarek Huang
  • 119
  • 3
  • 17

1 Answers1

1

Your embedding is wrong. Embedding a byte in RGBA pixels is easy because you can conveniently fit half a byte per pixel. But for RGB you can fit 3/8 of a byte, which is not an integer number. Here is a demonstration of how complicated it is for RGB (assume that we start at pixel (0, 0)):

// Byte 1
(0, 0, R) (0, 0, G) (0, 0, B)
(0, 1, R) (0, 1, G) (0, 1, B)
(0, 2, R) (0, 2, G)

// Byte 2
                    (0, 2, B)
(0, 3, R) (0, 3, G) (0, 3, B)
(0, 4, R) (0, 4, G) (0, 4, B)
(0, 5, R)

// Byte 3
          (0, 5, G) (0, 5, B)
(0, 6, R) (0, 6, G) (0, 6, B)
(0, 7, R) (0, 7, G) (0, 7, B)

As you can see, some times you need to embed to 3 or 4 different pixels and you don't always start/end from the R component. The code you need to achieve this is the following.

EncodeMessage

private void embedMessage(BufferedImage img, String mess) {
   int messageLength = mess.length();

   int imageWidth = img.getWidth(), imageHeight = img.getHeight(),
      imageSize = imageWidth * imageHeight;
   if((messageLength * 8 + 32)/3 > imageSize) {
      JOptionPane.showMessageDialog(this, "Message is too long for the chosen image",
         "Message too long!", JOptionPane.ERROR_MESSAGE);
      return;
      }
   embedInteger(img, messageLength, 0);

   byte b[] = mess.getBytes();
   for(int i=0; i<b.length; i++)
      embedByte(img, b[i], i*8+32);
   }

private void embedInteger(BufferedImage img, int n, int start) {
   int mod = start%3;
   start = start/3;
   int maxX = img.getWidth(), maxY = img.getHeight(), 
      startX = start/maxY, startY = start - startX*maxY, count=0;
   for(int i=startX; i<maxX && count<32; i++) {
      for(int j=startY; j<maxY && count<32; j++) {
         int rgb = img.getRGB(i, j), bit = 0, pp = 0;
         if(count <= 29) {
            for(pp=mod; pp<3; pp++) {
               bit = getBitValue(n, count); rgb = setBitValue(rgb, 8*pp, bit);
               count += 1;
               }
            mod = 0;
            }
         else {
            for(pp=0; pp<(33-count); pp++) {
               bit = getBitValue(n, count); rgb = setBitValue(rgb, 8*pp, bit);
               count += 1;
               }
            }
         img.setRGB(i, j, rgb);
         }
      }
   }

private void embedByte(BufferedImage img, byte b, int start) {
   int mod = start%3;
   start = start/3;
   int maxX = img.getWidth(), maxY = img.getHeight(), 
      startX = start/maxY, startY = start - startX*maxY, count=0;
   for(int i=startX; i<maxX && count<8; i++) {
      for(int j=startY; j<maxY && count<8; j++) {
         if(j==maxY-1){
            startY = 0;
            }
         int rgb = img.getRGB(i, j), bit = 0, pp = 0;
         if(count <= 5) {
            for(pp=mod; pp<3; pp++) {
               bit = getBitValue(b, count); rgb = setBitValue(rgb, 8*pp, bit);
               count += 1;
                  }
            mod = 0;
            }
         else {
            for(pp=0; pp<(9-count); pp++) {
               bit = getBitValue(b, count); rgb = setBitValue(rgb, 8*pp, bit);
               count += 1;
               }
            }        
         img.setRGB(i, j, rgb);
         }
      }
   }

DecodeMessage

private void decodeMessage() {
   int len = extractInteger(image, 0);
   byte b[] = new byte[len];
   for(int i=0; i<len; i++)
      b[i] = extractByte(image, i*8+32);
   message.setText(new String(b));
   }

private int extractInteger(BufferedImage img, int start) {
   int mod = start%3;
   start = start/3;
   int maxX = img.getWidth(), maxY = img.getHeight(), 
      startX = start/maxY, startY = start - startX*maxY, count=0;
   int length = 0;
   for(int i=startX; i<maxX && count<32; i++) {
      for(int j=startY; j<maxY && count<32; j++) {
         int rgb = img.getRGB(i, j), bit = 0, pp = 0;
         if(count <= 29) {
            for(pp=mod; pp<3; pp++) {
               bit = getBitValue(rgb, 8*pp); length = setBitValue(length, count, bit);
               count += 1;
               }
            mod = 0;
            }
         else {
            for(pp=0; pp<(33-count); pp++) {
               bit = getBitValue(rgb, 8*pp); length = setBitValue(length, count, bit);
               count += 1;
               }
            }
         }
      }
   return length;
   }

private byte extractByte(BufferedImage img, int start) {
   int mod = start%3;
   start = start/3;
   int maxX = img.getWidth(), maxY = img.getHeight(), 
      startX = start/maxY, startY = start - startX*maxY, count=0;
   byte b = 0;
   for(int i=startX; i<maxX && count<8; i++) {
      for(int j=startY; j<maxY && count<8; j++) {
         if(j==maxY-1){
            startY = 0;
            }
         int rgb = img.getRGB(i, j), bit = 0, pp = 0;
         if(count <= 5) {
            for(pp=mod; pp<3; pp++) {
               bit = getBitValue(rgb, 8*pp); b = (byte)setBitValue(b, count, bit);
               count += 1;
               }
            mod = 0;
            }
         else {
            for(pp=0; pp<(9-count); pp++) {
               bit = getBitValue(rgb, 8*pp); b = (byte)setBitValue(b, count, bit);
               count += 1;
               }
            }
         }
      }
   return b;
   }

I will briefly explain the logic behind this. Since it's similar for both encoding and decoding, I will only describe the former. And since embedInteger and embedByte are similar, I will only describe embedByte.

In embedMessage, we need to pass i*8+32 in embedByte because we need the number of bits we have embedded so far. This is necessary so to know where the embedding from the previous byte stopped (as shown above, after byte 1 we have to start from B, while after byte 2 from G). This is achieved with the modulo operation (int mod = start%3), which gives you the remainder of division by 3. For example, 8%3 = 2. For mod = 0, we start at R, for mod = 1 at G and for mod = 2 at B.

start = start/3 tells you at which pixel we have to start. An integer division gives you a rounded down integer result, so for example 8/3 = round down 2.666 = 2. As you can see, start and mod give us all the information for where we have to start. For example, after one byte we start at pixel 2, B component. Now we can start embedding the byte inside the i and j loops.

Inside the loops, we fetch our new rgb pixel. Depending on how many bits we have embedded so far, we may embed in the whole RGB, or just part of it. The object count tells us how many bits we have embedded so far and overall we embed 8 bits. This is where the if-else statement comes in. Effectively, we ask the question "do we have more than 3 bits left to embed?" This is translated as 8-count >= 3 and when this is algebraically rearranged, you get count <= 5. To sum up:

if: we have enough bits left to embed in all 3 components

else: we don't have enough bits left to embed in all 3 components

Now, pp decides in which colour component we embed our bit and it can take the values 0, 1 and 2. The java syntax for that is for(pp=0; p<3; pp++). That's just how it is. Then 8*pp can be 0, 8 or 16, which is the LSB for R, G or B.

In the if block, we have for(pp=mod; ...), because we may not start from 0. Look at the above example, where byte 2 has mod=2, because we start from the blue component. But once that loop is over, mod will be reset to 0, so for the next pixel we do start from 0.

To understand the else block (for(pp=0; pp<(9-count); p++)), look at the example from above for byte 1. We have already embedded in the first 2 pixels, so we have two more bits left. This means we have count=6.

pp=0; the condition to exit is pp<9-6 --> pp<3, so we continue. We embed the 7th bit in R --> count=7.

pp=1; the condition to exit is pp<9-7 --> pp<2, so we continue. We embed the 8th bit in G --> count=8.

pp=2; the condition to exit is pp<9-8 --> pp<1 --> WE HAVE TO EXIT.

A simpler equivalent to that logic is the following and I don't know why I don't go with this.

for(pp=0; pp<(8-count); pp++) {
   getBit...; setBit...;
   }
count = 8;
Reti43
  • 9,656
  • 3
  • 28
  • 44
  • In `embedMessage`, (i*8+32)/3, number 3 here, because R,G,B 3 components?? but why we don't do it when doing ARGB (like do (i*8+32)/4) – Jarek Huang Jan 26 '14 at 17:32
  • 1
    (i*8+32)/4 = i*2+8. It's just that the second requires fewer computations. – Reti43 Jan 26 '14 at 17:39
  • Sorry, i have last two question about the code. 1) why message need 32 bit for first, why not 24 bits(because i was think 32bit,because 4 component, or because int?). 2) why doing `count <= 29` in `embedInteger` and why 29, same as `embedByte`, why `count <= 5` and why 5?? – Jarek Huang Jan 26 '14 at 17:58
  • 1
    1) because the message length is a long integer and that requires 4 bytes. It has nothing to do with how many colour components you have. 2) count <= 29 is count <= 32-3, which means if you still have more than 3 bits left to embed, do this. Similarly, 5 = 8-3. – Reti43 Jan 26 '14 at 18:31
  • Not really get it. minus 3 because RGB can fit 3/8 of a byte, which is not an integer number?? – Jarek Huang Jan 26 '14 at 18:51
  • 1
    That 3 is because you embed in 3 components. It doesn't matter how many components the pixel has, but how many you want to embed in. For example, in a RGB pixel, if you want to embed in only two components, you would do minus 2. In an ARGB pixel, if you wanted to embed 3 components, you would do minus 3. Of course, you can't embed into more components than you have available, so in RGB, you can do up to 3. The if-else statement is there because it depends the occasion on whether you will embed 1, 2 or 3 bits in the current pixel. – Reti43 Jan 26 '14 at 19:01
  • in ARGB pexel will minus 4 if i want to embed to 4 component??, but it didnt do that when doing ARGB – Jarek Huang Jan 26 '14 at 19:04
  • 1
    Technically, yes. But you don't have to do it like this. Like I've shown you, embedding 4 bits in ARGB is MUCH simpler than 3 bits in RGB. This is because 8 (bits in a byte) / 4 (embedding bits in ARGB) = 2, which is a whole number, while 8/3 = 2.666. Embedding 4 bits in ARGB means you always embed one byte every two pixels and in each pixel, you embed in all 4 components. – Reti43 Jan 26 '14 at 19:13
  • "to embed in all 3 colour components `(pp<3)`. Otherwise, we embed in as many components required to reach a count of 8 `(pp<(9-count))`.", BUT i thought we just need embed in to 3 color components, but why need another `(pp<(9-count))`. And sorry still cant really understand why need minus 3, as you said because embed in 3 components, but why minus 3?? – Jarek Huang Jan 27 '14 at 21:30
  • Each component(or each pixel?) embed 1 bit?? If i have 1 byte(01011011) to embed, it will be embedded like this 0(embed to G)1(R)0(B)1(G)1(R)0(B)1(G)1(R) – Jarek Huang Jan 28 '14 at 01:01
  • 1
    You have the right idea, but it's the inverse. The bits embedded will be 11011010. While we write and read numbers from the left to right, from the least to the most significant digits, we go from right to left. Like the number 1234 is 4-3-2-1. But generally, you got the idea. – Reti43 Jan 28 '14 at 01:35
  • Hi Reti, I got some problem with decode part. Could you help me with this, because i could find the problem is. [link](http://stackoverflow.com/questions/21633505/exception-in-thread-thread-java-lang-outofmemoryerror-requested-array-size-ex) – Jarek Huang Feb 07 '14 at 16:57