Search code examples
javaimageimage-processingsteganographydct

DCT Steganography


I'm trying to complete DCT Steganography project and some questions regarding it occurred. Could anybody help me? What I've done:

1) split the image into 8x8 pixeles

public BufferedImage[] getBlocksOfImage(BufferedImage image){
    int width = image.getWidth();
    int height = image.getHeight();
    int arrayIndex = 0;
    //TODO specify dynamic length
    //TODO for 512x512 --> 4096 (8x8 block)
    BufferedImage[] blocksOfImage = new BufferedImage[4096];
    for (int i = 0; i<width; i=i+8) {
        for (int j = 0; j<height; j=j+8) {
            blocksOfImage[arrayIndex] = image.getSubimage(i,j,8,8);
            arrayIndex++;
        }
    }
    return blocksOfImage;
}

2) compute the matrix from each block using:

//get matrix of pixels (used in DCT]
public int[][] getMatrixPixels(BufferedImage image){
    int[][] matrixPixels = new int[image.getWidth()][image.getHeight()];
    for (int i = 0; i < matrixPixels[0].length; i++) {
        for (int j = 0; j < matrixPixels.length; j++) {
            matrixPixels[i][j] = image.getRGB(i, j);
        }
    }
    return matrixPixels;
}

3) and from the block, I am getting computed DCT matrix using:

public double[][] getDTCTransformMatrix(int[][] imageMatrix){

    double[][] dctTransformMatrix = new double[m][n];
    double ci, cj, tmpDCTValue, tmpSum;
    tmpSum = 0;

    //TODO simplify 4x for --> BAD
    //TODO consulting
    for(int i = 0; i<m; i++) {
        for(int j = 0; j<n; j++) {

            if (i == 0){
                ci = 1 / Math.sqrt(m);
            } else {
                ci = Math.sqrt(2) / Math.sqrt(m);
            }
            if (j == 0){
                cj = 1 / Math.sqrt(n);
            } else {
                cj = Math.sqrt(2) / Math.sqrt(m);
            }

            for (int i_image = 0; i_image < m; i_image++) {
                for (int j_image = 0; j_image < n; j_image++) {

                    tmpDCTValue = imageMatrix[i_image][j_image] *
                            Math.cos((2 * i_image + 1) * i * Math.PI / (2 * m)) *
                            Math.cos((2 * j_image + 1) * j * Math.PI / (2 * n));
                    tmpSum += tmpDCTValue;
                }
            }
            dctTransformMatrix[i][j] = ci * cj * tmpSum;
        }
    }
    return dctTransformMatrix;
}

Here are my questions:

1) What should I pass into getDTCTransformMatrix() function? Right now I am passing integer value of the pixel, which is wrong I think. I have seen some examples where guys were passing value from 0-255 so should I convert image into gray-scale? Or should I do it for each color (R,G,B)?

2) After getDTCTransformMatrix() is performed I get matrix of double. How can I edit LSB in double value? And is this a correct approach?

3) After I will change an LSB in double value what should I do next? How to make sure that information will be stored in the image.

Thank you all for answers:)

--- EDIT ---

I've edited the code and now I'm passing each channel separately (R, G,B) and I added following functions:

public int[][] getQuantiseCoefficients(double[][] DTCTransformMatrix) {
    int[][] quantiseResult = new int[DTCTransformMatrix.length][DTCTransformMatrix[1].length];
    int[][] quantiseMatrix =   {{16, 11, 10, 16, 24, 40, 51, 61},
                                {12, 12, 14, 19, 26, 58, 60, 55},
                                {14, 13, 16, 24, 40, 57, 69, 56},
                                {14, 17, 22, 29, 51, 87, 80, 62},
                                {18, 22, 37, 56, 68, 109, 103, 77},
                                {24, 35, 55, 64, 81, 104, 113, 92},
                                {49, 64, 78, 87, 103, 121, 120, 101},
                                {72, 92, 95, 98, 112, 100, 103, 99}};

    //TODO delete static 8
    for (int i = 0; i<8; i++) {
        for (int j = 0; j<8; j++) {
            //Bij = round(Gij/Qij)
            quantiseResult[i][j] = (int)Math.round(DTCTransformMatrix[i][j]/quantiseMatrix[i][j]);
        }
    }
    return quantiseResult;
}

and for adding the message:

public ArrayList<int[][]> addMessageFirst(ArrayList<int[][]> quantiseMatrixRGB, String message) {
    Utils utils = new Utils();
    int bitTextShift = 7;
    int byteTextShift = 0;
    byte[] textByteArray = text.getBytesFromText(message);

    for (int i = 0; i < textByteArray.length*8; i++) {
        byte[] firstValueByte = utils.integerToByte(quantiseMatrixRGB.get(i)[0][0]);

        firstValueByte[3] = (byte) ((firstValueByte[3] & 0xFE) | ((int) textByteArray[byteTextShift] >>> bitTextShift) & 1);
        if (bitTextShift <= 0) {
            bitTextShift = 7;
            byteTextShift++;
        } else {
            bitTextShift--;
        }
    }
    return quantiseMatrixRGB;
}

Everything seems to work fine but now I am struggling with putting an image back together. The code I've done is following but I think this is quite difficult. Is not there a more simple way?

For example by using some already created JPEG encoder?

public int[][] revertGetQuantiseCoefficients (int[][] quantiseMatrixRGB) {
    int[][] quantiseMatrix =   {{16, 11, 10, 16, 24, 40, 51, 61},
                                {12, 12, 14, 19, 26, 58, 60, 55},
                                {14, 13, 16, 24, 40, 57, 69, 56},
                                {14, 17, 22, 29, 51, 87, 80, 62},
                                {18, 22, 37, 56, 68, 109, 103, 77},
                                {24, 35, 55, 64, 81, 104, 113, 92},
                                {49, 64, 78, 87, 103, 121, 120, 101},
                                {72, 92, 95, 98, 112, 100, 103, 99}};
    for (int j = 0; j<8; j++) {
        for (int k = 0; k<8; k++) {
            quantiseMatrixRGB[j][k]=quantiseMatrixRGB[j][k]*quantiseMatrix[j][k];
        }
    }
    return quantiseMatrixRGB;
}

public int[][] revertGetDTCTransformMatrix(int[][] quantiseMatrixRGB) {
    double[][] dctTransformMatrix = new double[m][n];
    double ck, cl, tmpDCTValue, tmpSum;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                tmpSum = 0;
                for (int i_image = 0; i_image < m; i_image++) {
                    for (int j_image = 0; j_image < n; j_image++) {

                        if (i_image == 0) {
                            ck = 1 / Math.sqrt(m);
                        } else {
                            ck = Math.sqrt(2) / Math.sqrt(m);
                        }
                        if (j_image == 0) {
                            cl = 1 / Math.sqrt(n);
                        } else {
                            cl = Math.sqrt(2) / Math.sqrt(n);
                        }

                        tmpDCTValue = quantiseMatrixRGB[i_image][j_image] *
                                Math.cos((2 * i_image + 1) * i * pi / (2 * m)) *
                                Math.cos((2 * j_image + 1) * j * pi / (2 * n));
                        tmpSum = tmpSum + ck*cl*tmpDCTValue;
                    }
                }
                dctTransformMatrix[i][j] =  tmpSum;
            }
        }
    return quantiseMatrixRGB;
}

public int[][] getMergeRGB (int[][] r, int[][] g, int[][] b) {
    int[][] mergeRGB = new int[r.length][r[1].length];
    for (int i = 0; i<8; i++) {
        for(int j = 0; j<8; j++) {
            mergeRGB[i][j] = r[i][j];
            mergeRGB[i][j] = (mergeRGB[i][j]<<8) + g[i][j];
            mergeRGB[i][j] = (mergeRGB[i][j]<<8) + b[i][j];
        }
    }
    return mergeRGB;
}

public BufferedImage getPixelBlock (int[][] mergeRGB) {
    BufferedImage bi = new BufferedImage( 8, 8, BufferedImage.TYPE_INT_RGB );
    final int[] a = ( (DataBufferInt) bi.getRaster().getDataBuffer() ).getData();
    System.arraycopy(mergeRGB, 0, a, 0, mergeRGB.length);
    return bi;
}

public BufferedImage joinImages (BufferedImage subImage) {


    return null;
}
}

And I'm struggling with putting BufferedImage subImages back together. Any ideas?

Thank you very much for your time and afford.


Solution

  • It seems like you're trying to do JPEG steganography. The standard for writing a jpeg encoder is quite involving, so it's easier to borrow an already written one and make any small modifications to inject your hiding algorithm in it.

    I've answered a similar question where I briefly summarised the key points of the algorithm and showed an example in java.

    A good start to learn about the process is on wikipedia on JPEG encoding. This should answer all questions you've posed, but I'll address them here as well.

    What should I pass into getDTCTransformMatrix() function? Right now I am passing integer value of the pixel, which is wrong I think. I have seen some examples where guys were passing value from 0-255 so should I convert image into gray-scale? Or should I do it for each color (R,G,B)?

    Yes, you do the pass integer values of each colour plane individually. That can either be in RGB or YCrCb. And it can also be from 0-255, or centred around 0, i.e., -127,128. The reason why YCrCb is preferred is because some of the channels can be compressed more without any obvious loss of quality due to how our eyes work. And shifting the range of the numbers to be centred around 0 means that the resulting DCT coefficients will have smaller values and will take fewer bits to store.

    After getDTCTransformMatrix() is performed I get matrix of double. How can I edit LSB in double value? And is this a correct approach?

    This is the main point of JPEG encoding. You're supposed to quantise the coefficients (turn them to integers) with a specific quantisation matrix. While there is a default one, various programs opt to use custom ones. The idea is that the low frequency coefficients will not be affected a lot, while most of the high frequency ones may become 0, which aids in the final file size being smaller. This is a lossy process and you're supposed to embed your information AFTER you have quantised the coefficients, as the remaining steps are all lossless.

    After I will change an LSB in double value what should I do next? How to make sure that information will be stored in the image.

    You arrange the coefficients in a zigzag pattern in a 1D, so that the low frequency coefficients come first. You then use a combination of run-length and Huffman encoding to store that information in a file. The binary data of the file bear no resemblance to either the original pixels (obviously) or the values of the DCT coefficients. It's compressed data of the coefficients.