📄 jpegencoder.java
字号:
// Version 1.0a// Copyright (C) 1998, James R. Weeks and BioElectroMech.// Visit BioElectroMech at www.obrador.com. Email James@obrador.com.// See license.txt for details about the allowed used of this software.// This software is based in part on the work of the Independent JPEG Group.// See IJGreadme.txt for details about the Independent JPEG Group's license.// This encoder is inspired by the Java Jpeg encoder by Florian Raemy,// studwww.eurecom.fr/~raemy.// It borrows a great deal of code and structure from the Independent// Jpeg Group's Jpeg 6a library, Copyright Thomas G. Lane.// See license.txt for details.// westfeld// todo:// switch for multi-volume embedding// indeterministic embedding// password switchpackage james;import crypt.Permutation;import crypt.F5Random;import java.applet.Applet;import java.awt.*;import java.awt.image.*;import java.io.*;import java.util.*;import java.lang.*;/** JpegEncoder - The JPEG main program which performs a jpeg compression of* an image.*/public class JpegEncoder extends Frame{ Thread runner; BufferedOutputStream outStream; Image image; JpegInfo JpegObj; Huffman Huf; DCT dct; int imageHeight, imageWidth; int Quality; int code; public static int[] jpegNaturalOrder = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, }; // westfeld FileInputStream embeddedData = null; String password = null; int n = 0; public JpegEncoder(Image image, int quality, OutputStream out, String comment) { MediaTracker tracker = new MediaTracker(this); tracker.addImage(image, 0); try { tracker.waitForID(0); } catch (InterruptedException e) {// Got to do something? } /* * Quality of the image. * 0 to 100 and from bad image quality, high compression to good * image quality low compression */ Quality=quality; /* * Getting picture information * It takes the Width, Height and RGB scans of the image. */ JpegObj = new JpegInfo(image, comment); imageHeight=JpegObj.imageHeight; imageWidth=JpegObj.imageWidth; outStream = new BufferedOutputStream(out); dct = new DCT(Quality); Huf=new Huffman(imageWidth,imageHeight); } public void setQuality(int quality) { dct = new DCT(quality); } public int getQuality() { return Quality; } public void Compress(FileInputStream embeddedData, String password) { this.embeddedData = embeddedData; this.password = password; Compress(); } public void Compress() { WriteHeaders(outStream); WriteCompressedData(outStream); WriteEOI(outStream); try { outStream.flush(); } catch (IOException e) { System.out.println("IO Error: " + e.getMessage()); } } public void WriteCompressedData(BufferedOutputStream outStream) { int offset, i, j, r, c,a ,b, temp = 0; int comp, xpos, ypos, xblockoffset, yblockoffset; float inputArray[][]; float dctArray1[][] = new float[8][8]; double dctArray2[][] = new double[8][8]; int dctArray3[] = new int[8*8]; /* * This method controls the compression of the image. * Starting at the upper left of the image, it compresses 8x8 blocks * of data until the entire image has been compressed. */ int lastDCvalue[] = new int[JpegObj.NumberOfComponents]; int zeroArray[] = new int[64]; // initialized to hold all zeros int Width = 0, Height = 0; int nothing = 0, not; int MinBlockWidth, MinBlockHeight;// This initial setting of MinBlockWidth and MinBlockHeight is done to// ensure they start with values larger than will actually be the case. MinBlockWidth = ((imageWidth%8 != 0) ? (int) (Math.floor((double) imageWidth/8.0) + 1)*8 : imageWidth); MinBlockHeight = ((imageHeight%8 != 0) ? (int) (Math.floor((double) imageHeight/8.0) + 1)*8: imageHeight); for (comp = 0; comp < JpegObj.NumberOfComponents; comp++) { MinBlockWidth = Math.min(MinBlockWidth, JpegObj.BlockWidth[comp]); MinBlockHeight = Math.min(MinBlockHeight, JpegObj.BlockHeight[comp]); } xpos = 0; // westfeld // Before we enter these loops, we initialise the // coeff for steganography here: int shuffledIndex = 0; int coeffCount = 0; for (r = 0; r < MinBlockHeight; r++) { for (c = 0; c < MinBlockWidth; c++) { for (comp = 0; comp < JpegObj.NumberOfComponents; comp++) { for(i = 0; i < JpegObj.VsampFactor[comp]; i++) { for(j = 0; j < JpegObj.HsampFactor[comp]; j++) { coeffCount += 64; } } } } } int coeff[] = new int[coeffCount];System.out.println("DCT/quantisation starts");System.out.println(imageWidth+" x "+imageHeight); for (r = 0; r < MinBlockHeight; r++) { for (c = 0; c < MinBlockWidth; c++) { xpos = c*8; ypos = r*8; for (comp = 0; comp < JpegObj.NumberOfComponents; comp++) { Width = JpegObj.BlockWidth[comp]; Height = JpegObj.BlockHeight[comp]; inputArray = (float[][]) JpegObj.Components[comp]; for(i = 0; i < JpegObj.VsampFactor[comp]; i++) { for(j = 0; j < JpegObj.HsampFactor[comp]; j++) { xblockoffset = j * 8; yblockoffset = i * 8; for (a = 0; a < 8; a++) { for (b = 0; b < 8; b++) {// I believe this is where the dirty line at the bottom of the image is// coming from. I need to do a check here to make sure I'm not reading past// image data.// This seems to not be a big issue right now. (04/04/98)// westfeld - dirty line fixed, Jun 6 2000 int ia = ypos*JpegObj.VsampFactor[comp] + yblockoffset + a; int ib = xpos*JpegObj.HsampFactor[comp] + xblockoffset + b; if (imageHeight/2*JpegObj.VsampFactor[comp]<=ia) ia = imageHeight/2*JpegObj.VsampFactor[comp]-1; if (imageWidth/2*JpegObj.HsampFactor[comp]<=ib) ib = imageWidth/2*JpegObj.HsampFactor[comp]-1; //dctArray1[a][b] = inputArray[ypos + yblockoffset + a][xpos + xblockoffset + b]; dctArray1[a][b] = inputArray[ia][ib]; } }// The following code commented out because on some images this technique// results in poor right and bottom borders.// if ((!JpegObj.lastColumnIsDummy[comp] || c < Width - 1) && (!JpegObj.lastRowIsDummy[comp] || r < Height - 1)) { dctArray2 = dct.forwardDCT(dctArray1); dctArray3 = dct.quantizeBlock(dctArray2, JpegObj.QtableNumber[comp]);// }// else {// zeroArray[0] = dctArray3[0];// zeroArray[0] = lastDCvalue[comp];// dctArray3 = zeroArray;// } // westfeld // For steganography, all dct // coefficients are collected in // coeff[] first. We do not encode // any Huffman Blocks here (we'll do // this later). System.arraycopy(dctArray3, 0, coeff, shuffledIndex, 64); shuffledIndex += 64; } } } } }System.out.println("got "+coeffCount+" DCT AC/DC coefficients"); int _changed=0; int _embedded=0; int _examined=0; int _expected=0; int _one=0; int _large=0; int _thrown=0; int _zero=0; for (i=0; i<coeffCount; i++) { if ((i%64)==0) continue; if (coeff[i]==1) _one++; if (coeff[i]==-1) _one++; if (coeff[i]==0) _zero++; } _large=coeffCount-_zero-_one-coeffCount/64; _expected=_large+(int)(0.49*_one);//// System.out.println("zero="+_zero);System.out.println("one="+_one);System.out.println("large="+_large);//System.out.println("expected capacity: "+_expected+" bits");System.out.println("expected capacity with");for (i=1; i<8; i++) { int usable, changed, n; n = (1<<i)-1; usable = _expected*i/n-_expected*i/n%n; changed = coeffCount-_zero-coeffCount/64; changed = changed*i/n-changed*i/n%n; changed = n*changed/(n+1)/i; // changed = _large-_large%(n+1); changed = (changed+_one+_one/2-_one/(n+1))/(n+1); usable /= 8; if (usable == 0) break; if (i==1) System.out.print("default"); else System.out.print("(1, "+n+", "+i+")"); System.out.println(" code: "+usable+" bytes (efficiency: " +((usable*8)/changed)+"."+(((usable*80)/changed)%10) +" bits per change)");} // westfeld if (embeddedData != null) { // Now we embed the secret data in the permutated sequence.System.out.println("Permutation starts"); F5Random random = new F5Random(password.getBytes()); Permutation permutation = new Permutation(coeffCount, random); int nextBitToEmbed=0; int byteToEmbed=0; int availableBitsToEmbed=0; // We start with the length information. Well, // the length information it is more than one // byte, so this first "byte" is 32 bits long. try { byteToEmbed=embeddedData.available(); } catch (Exception e) { e.printStackTrace(); } System.out.print("Embedding of "+(byteToEmbed*8+32)+" bits (" +byteToEmbed+"+4 bytes) "); // We use the most significant byte for the 1 of n // code, and reserve one extra bit for future use. if (byteToEmbed>0x007fffff) byteToEmbed=0x007fffff; // We calculate n now for (i=1; i<8; i++) { int usable, changed; n = (1<<i)-1; usable = _expected*i/n-_expected*i/n%n; usable /= 8; if (usable == 0) break; if (usable < byteToEmbed+4) break; } int k=i-1; n=(1<<k)-1; switch (n) { case 0: System.out.println("using default code, file will not fit"); n++; break; case 1: System.out.println("using default code"); break; default: System.out.println("using (1, "+n+", "+k+") code"); } byteToEmbed |= k<<24; // store k in the status word // Since shuffling cannot hide the distribution, the // distribution of all bits to embed is unified by // adding a pseudo random bit-string. We continue the random // we used for Permutation, initially seeked with password. byteToEmbed ^= random.getNextByte(); byteToEmbed ^= random.getNextByte()<<8; byteToEmbed ^= random.getNextByte()<<16; byteToEmbed ^= random.getNextByte()<<24; nextBitToEmbed = byteToEmbed & 1; byteToEmbed >>= 1; availableBitsToEmbed=31; _embedded++; if (n > 1) { // use 1 of n code int kBitsToEmbed; int extractedBit; int[] codeWord = new int[n]; int hash; int startOfN=0; int endOfN=0; boolean isLastByte = false; // embed status word first for(i=0; i<coeffCount; i++) { shuffledIndex = permutation.getShuffled(i); if (shuffledIndex%64 == 0) continue; // skip DC coefficients if (coeff[shuffledIndex] == 0) continue; // skip zeroes if (coeff[shuffledIndex] > 0) { if ((coeff[shuffledIndex]&1) != nextBitToEmbed) { coeff[shuffledIndex]--; // decrease absolute value _changed++; } } else { if ((coeff[shuffledIndex]&1) == nextBitToEmbed) { coeff[shuffledIndex]++; // decrease absolute value _changed++; } } if (coeff[shuffledIndex] != 0) { // The coefficient is still nonzero. We // successfully embedded "nextBitToEmbed". // We will read a new bit to embed now. if (availableBitsToEmbed==0) break; // statusword embedded. nextBitToEmbed = byteToEmbed & 1; byteToEmbed >>= 1; availableBitsToEmbed--; _embedded++; } else _thrown++; } startOfN = i+1; // now embed the data using 1 of n codeembeddingLoop: do { kBitsToEmbed = 0; // get k bits to embed for (i=0; i<k; i++) { if (availableBitsToEmbed==0) { // If the byte of embedded text is // empty, we will get a new one. try { if (embeddedData.available()==0) { isLastByte = true; break; } byteToEmbed = embeddedData.read(); byteToEmbed ^= random.getNextByte(); } catch (Exception e) { e.printStackTrace(); break; } availableBitsToEmbed=8; } nextBitToEmbed = byteToEmbed & 1; byteToEmbed >>= 1; availableBitsToEmbed--; kBitsToEmbed |= nextBitToEmbed << i; _embedded++; } // embed k bits do { j = startOfN; // fill codeWord[] with the indices of the // next n non-zero coefficients in coeff[] for (i=0; i<n; j++) { if (j>=coeffCount) { // in rare cases the estimated capacity is too small System.out.println("Capacity exhausted."); break embeddingLoop; } shuffledIndex = permutation.getShuffled(j); if (shuffledIndex%64 == 0) continue; // skip DC coefficients if (coeff[shuffledIndex] == 0) continue; // skip zeroes codeWord[i++]=shuffledIndex; } endOfN = j; hash = 0; for (i=0; i<n; i++) { if (coeff[codeWord[i]] > 0) extractedBit = coeff[codeWord[i]]&1; else extractedBit = 1-(coeff[codeWord[i]]&1); if (extractedBit == 1) hash ^= i+1; } i = hash ^ kBitsToEmbed; if (i==0) break; // embedded without change i--; if (coeff[codeWord[i]]>0) coeff[codeWord[i]]--; else coeff[codeWord[i]]++; _changed++; if (coeff[codeWord[i]]==0) _thrown++; } while (coeff[codeWord[i]]==0); startOfN = endOfN; } while (!isLastByte); } else { // default code // The main embedding loop follows. It works on the // shuffled stream of coefficients. for(i=0; i<coeffCount; i++) { shuffledIndex = permutation.getShuffled(i); if (shuffledIndex%64 == 0) continue; // skip DC coefficients
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -