0

I am trying to replace the two least significant bits in each red, green, and blue value in a buffered image pixel in Scala. But when I try to change the least significant bits the change doesn't hold. I have got this working when changing the two most significant bits but it won't work using least significant. Here is the method that does most of the heavy lifting

  def hideDataInImage(dataToHide: String, imageToHideIn: BufferedImage): Option[BufferedImage] = {
      // Check that the binary data is short enough to encode in the image. Assumeing that we can hide
      // one 6-bit element in one pixel the size of the array must be less than the area of the array
      if(dataToHide.length > (imageToHideIn.getHeight() * imageToHideIn.getWidth())) {
          return None
      }

     val imageToHideInCopy = deepCopyBufferedImage(imageToHideIn)

     // Turn the string of data into a string of binary numbers
     val binaryToHide = stringToBinaryArray(dataToHide)

     // Loop through the binary data and hide it in each pixel
     for(i <- binaryToHide.indices) {
         // Get the x and y data for the pixel
         val y = i / imageToHideInCopy.getWidth()
         val x = i % imageToHideInCopy.getHeight()

         val newColor = hideDataInColor(imageToHideInCopy.getRGB(x, y), binaryToHide(i))
         imageToHideInCopy.setRGB(x, y, newColor.getRGB)
      }

     // Return some image to hide in if success
     Some(imageToHideInCopy)

}

The rest of the code can be seen on my Github.

Edit: Here is the rest of my code

def hideDataInColor(pixelValue: Int, binaryDataToHide: String): Color = {
    // Get the red green blue and alpha values from the pixel value
    //           3        2        1        0
    // bitpos    10987654 32109876 54321098 76543210
    // ------   +--------+--------+--------+--------+
    // bits     |AAAAAAAA|RRRRRRRR|GGGGGGGG|BBBBBBBB|

    val blueValue = byteToPaddedBinary(pixelValue & 0xff, 8)
    val greenValue = byteToPaddedBinary((pixelValue & 0xff00) >> 8, 8)
    val redValue = byteToPaddedBinary((pixelValue & 0xff0000) >> 16, 8)
    val alphaValue = byteToPaddedBinary((pixelValue & 0xff000000) >>> 24, 8)


    // Split the binarydata into three parts
    val splitData = binaryDataToHide.split("(?<=\\G.{" + binaryDataToHide.length / 3 + "})")

    // Get the modified red blue green values
    val newRed = hideBitsInBinaryString(redValue, splitData(0))
    val newGreen = hideBitsInBinaryString(greenValue, splitData(1))
    val newBlue = hideBitsInBinaryString(blueValue, splitData(2))

    // Convert the binary number to an integer and return it
    new Color(Integer.parseInt(newRed, 2), Integer.parseInt(newGreen, 2),
      Integer.parseInt(newBlue, 2), Integer.parseInt(alphaValue, 2))
}


def hideBitsInBinaryString(binaryString: String, bitsToHide: String): String = {
    // Create a string builder from the binary string
    val binaryStringStringBuilder = new StringBuilder(binaryString)

    // Loop through the bits to hide
    for(i <- 0 until bitsToHide.length) {
      // replace to chars at the end of the string builder
      binaryStringStringBuilder.setCharAt(binaryString.length - bitsToHide.length + i, bitsToHide(i))
      // binaryStringStringBuilder.setCharAt(i, bitsToHide(i))
    }

    // Return the binary string builder to string
    binaryStringStringBuilder.toString()
  }
Alex Day
  • 39
  • 1
  • 7
  • Could you please somehow move the relevant parts into the question instead of just linking to your github page? Why are you actually tweaking colors and pixel (x,y)-positions, wouldn't it be much easier to grab `pixels` in ARGB format and work on the integers directly? And what exactly do you mean by "the change does not hold"? – Andrey Tyukin Mar 22 '18 at 00:01
  • @AndreyTyukin I am getting the actual integer value for the x and y positions and am separating those into their binary strings and modifying the last two bits of each of those. I will edit my post and include the rest of the relevant code – Alex Day Mar 22 '18 at 00:03
  • Imho, it would be much simpler and less error prone if you didn't care about `(x,y)` but instead worked with the pixel index directly (what do you care about the layout of the pixels anyway?), and if there were no `Color` objects on which you could call `.getRGB`. Don't just "include the rest of the relevant code", try to isolate the actual problem as much as possible. Maybe re-read [MCVE](https://stackoverflow.com/help/mcve). – Andrey Tyukin Mar 22 '18 at 00:04

1 Answers1

1

You shouldn't have x and y coordinates anywhere in your code. The layout of the pixels in memory is completely irrelevant, you don't care about the original image anyway. You should work with the underlying linear pixels array directly, and address the pixels by index.

The (x, y)-coordinates are actively harmful, they cause at least one bug in your code. This here is wrong:

     val y = i / imageToHideInCopy.getWidth()
     val x = i % imageToHideInCopy.getHeight()

You should use either getWidth() getWidth() or getHeight() getHeight(), but don't mix both.

Smaller demo to understand what's going wrong. Suppose your image is 3x5:

scala> val w = 5
w: Int = 5

scala> val h = 3
h: Int = 3

This is what you are doing:

scala> val coords = (0 until 15).map{ i => (i / w, i % h) }
coords: scala.collection.immutable.IndexedSeq[(Int, Int)] = Vector(
  (0,0), (0,1), (0,2), (0,0), (0,1), 
  (1,2), (1,0), (1,1), (1,2), (1,0), 
  (2,1), (2,2), (2,0), (2,1), (2,2)
)

Notice how e.g. (0,0) and (0,1) appear twice. The information gets written to those pixels twice.

You should do either this:

scala> val coords = (0 until 15).map{ i => (i / w, i % w) }
coords: scala.collection.immutable.IndexedSeq[(Int, Int)] = Vector(
  (0,0), (0,1), (0,2), (0,3), (0,4), 
  (1,0), (1,1), (1,2), (1,3), (1,4), 
  (2,0), (2,1), (2,2), (2,3), (2,4)
)

Or this:

scala> val coords = (0 until 15).map{ i => (i / h, i % h) }
coords: scala.collection.immutable.IndexedSeq[(Int, Int)] = Vector(
  (0,0), (0,1), (0,2), 
  (1,0), (1,1), (1,2), 
  (2,0), (2,1), (2,2), 
  (3,0), (3,1), (3,2), 
  (4,0), (4,1), (4,2)
)

As you see, whether it's row-major or column-major is completely irrelevant anyway. So you can actually throw this error prone code away and use pixels directly.


That bit manipulation code... well, that's not bit manipulation code. Strings and regex shouldn't be anywhere close to this code. Just read ten random stackoverflow answers about bit manipulation and colors, for example something like this or this, or find some tutorial, just to get a feeling for what the code should look like.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93