/// <summary> /// 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. /// </summary> /// <param name="out"></param> private void WriteCompressedData() { // This initial setting of MinBlockWidth and MinBlockHeight is done to // ensure they start with values larger than will actually be the case. int MinBlockWidth = this.imageWidth % 8 != 0 ? (int)(Math.Floor(this.imageWidth / 8.0) + 1) * 8 : this.imageWidth; int MinBlockHeight = this.imageHeight % 8 != 0 ? (int)(Math.Floor(this.imageHeight / 8.0) + 1) * 8 : this.imageHeight; int comp, shuffledIndex; for (comp = 0; comp < JpegInfo.NumberOfComponents; comp++) { MinBlockWidth = Math.Min(MinBlockWidth, this.JpegObj.BlockWidth[comp]); MinBlockHeight = Math.Min(MinBlockHeight, this.JpegObj.BlockHeight[comp]); } int[] lastDCvalue = new int[JpegInfo.NumberOfComponents]; int[] emptyArray = new int[64]; int[] coeff = GetCoeff(MinBlockWidth, MinBlockHeight); int coeffCount = coeff.Length; int i, j, r, c; int _changed = 0; int _embedded = 0; int _examined = 0; int _expected = 0; int _one = 0; int _large = 0; int _thrown = 0; int _zero = 0; logger.Info("got " + coeffCount + " DCT AC/DC coefficients"); for (i = 0; i < coeffCount; i++) { if (i % 64 == 0) { continue; } else if (coeff[i] == 1 || coeff[i] == -1) { _one++; } else if (coeff[i] == 0) { _zero++; } } _large = coeffCount - _zero - _one - coeffCount / 64; _expected = _large + (int)(0.49 * _one); // logger.Info("one=" + _one); logger.Info("large=" + _large); // logger.Info("expected capacity: " + _expected + " bits"); logger.Info("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) { logger.Info("default"); } else { logger.Info("(1, " + n + ", " + i + ")"); } logger.Info(" code: " + usable + " bytes (efficiency: " + usable * 8 / changed + "." + usable * 80 / changed % 10 + " bits per change)"); } // westfeld if (this.embeddedData != null) { // Now we embed the secret data in the permutated sequence. logger.Info("Permutation starts"); F5Random random = new F5Random(Encoding.ASCII.GetBytes(this.password)); Permutation permutation = new Permutation(coeffCount, random); int nextBitToEmbed = 0; int byteToEmbed = Convert.ToInt32(this.embeddedData.Length); 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 = this.embeddedData.available(); * } catch (final Exception e) { * e.printStackTrace(); * }*/ logger.Info("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; this.n = (1 << i) - 1; usable = _expected * i / this.n - _expected * i / this.n % this.n; usable /= 8; if (usable == 0) { break; } if (usable < byteToEmbed + 4) { break; } } int k = i - 1; this.n = (1 << k) - 1; switch (this.n) { case 0: logger.Info("using default code, file will not fit"); this.n++; break; case 1: logger.Info("using default code"); break; default: logger.Info("using (1, " + this.n + ", " + k + ") code"); break; } 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++; for (i = 0; i < permutation.Length; i++) { int shuffled_index = permutation.GetShuffled(i); if (shuffled_index % 64 == 0 || coeff[shuffled_index] == 0) { continue; } var cc = coeff[shuffled_index]; _examined += 1; if (cc > 0 && (cc & 1) != nextBitToEmbed) { coeff[shuffled_index]--; _changed++; } else if (cc < 0 && (cc & 1) == nextBitToEmbed) { coeff[shuffled_index]++; _changed++; } if (coeff[shuffled_index] != 0) { if (availableBitsToEmbed == 0) { if (n > 1 || embeddedData.Available == 1) { break; } byteToEmbed = embeddedData.Read(); byteToEmbed ^= random.GetNextByte(); availableBitsToEmbed = 8; } nextBitToEmbed = byteToEmbed & 1; byteToEmbed >>= 1; availableBitsToEmbed--; _embedded++; } else { _thrown++; } } if (n > 1) { bool isLastByte = false; FilteredCollection filtered_index = permutation.Filter(coeff, i + 1); while (!isLastByte) { int kBitsToEmbed = 0; for (i = 0; i < k; i++) { if (availableBitsToEmbed == 0) { if (embeddedData.Available == 0) { isLastByte = true; break; } byteToEmbed = embeddedData.Read(); byteToEmbed ^= random.GetNextByte(); availableBitsToEmbed = 8; } nextBitToEmbed = byteToEmbed & 1; byteToEmbed >>= 1; availableBitsToEmbed--; kBitsToEmbed |= nextBitToEmbed << i; _embedded++; } List <int> codeWord = filtered_index.Offer(this.n); int extractedBit; while (true) { int vhash = 0; int count = codeWord.Count; for (i = 0; i < count; i++) { int index = codeWord[i]; extractedBit = coeff[index] > 0 ? coeff[index] & 1 : (1 - (coeff[index] & 1)); if (extractedBit == 1) { vhash ^= i + 1; } } i = vhash ^ kBitsToEmbed; if (i == 0) { break; } i--; if (coeff[codeWord[i]] < 0) { coeff[codeWord[i]]++; } else { coeff[codeWord[i]]--; } _changed++; if (coeff[codeWord[i]] == 0) { _thrown++; codeWord.RemoveAt(i); codeWord.Add(filtered_index.Offer()); } } } } } logger.Info("Starting Huffman Encoding."); shuffledIndex = 0; for (r = 0; r < MinBlockHeight; r++) { for (c = 0; c < MinBlockWidth; c++) { for (comp = 0; comp < JpegInfo.NumberOfComponents; comp++) { for (i = 0; i < JpegObj.VsampFactor[comp]; i++) { for (j = 0; j < JpegObj.HsampFactor[comp]; j++) { Array.Copy(coeff, shuffledIndex, emptyArray, 0, 64); this.huffman.HuffmanBlockEncoder(this.output, emptyArray, lastDCvalue[comp], this.JpegObj.DCtableNumber[comp], this.JpegObj.ACtableNumber[comp]); lastDCvalue[comp] = emptyArray[0]; shuffledIndex += 64; } } } } } this.huffman.FlushBuffer(this.output); }