将文件内容隐藏在bmp位图中

时间:2023-03-09 22:06:38
将文件内容隐藏在bmp位图中

首先要实现这个功能,你必须知道bmp位图文件的格式,这里我就不多说了,请看:http://www.cnblogs.com/xiehy/archive/2011/06/07/2074405.html

接下来主要讲解实现的思路和源码:

实现思路:
根据bmp的文件的格式(记录了文件大小,文件数据的位置等信息)和读取文件内容的方式(只读取指定偏移点的数据),
可得出:当我们改变数据偏移点的值和文件的大小,将要隐藏的文件内容保存在头部到偏移点的区域即可。

实现步骤:
1、解析整个文件的格式信息
2、获取偏移点位置
3、定位到调色板结束位置,将文件数据插入到调色板结束位置后面
4、修改偏移位置,加上要隐藏文件的大小
5、重新写入文件中

读取文件步骤:
1、解析bmp文件格式
2、获取偏移位置end和比特/像素和颜色索引数目
3、定位到调色板的结束位置,即数据的开始位置start
4、读取start到end之间的数据到文件中,即为原来文件的内容

根据上述实现步骤,初步的实现已完成,后期完善某些不足之处,例读取位图信息时使用byte数组存储,
这样如果文件过大,可能会溢出

优化:
1、基本类型的字节的优化,避免强制转换
2、位图数据可以不存储,在需要写入的时候再去读原文件的位图数据部分
3、调色板数据在这个方法里也可以不存储,但其实不会很大,所以也没多大关系,可做可不做
4、抽除掉重复功能的代码

思考:
可以直接将文件数据写入到位图数据的最后面?
可以,这个更加的简单

实现步骤:
1、解析总的文件大小
2、读取bmp所有的数据到新的文件中
3、读取将要隐藏的文件的内容,写入到新的文件中

读取文件内容步骤:
1、解析出原来bmp文件的大小
2、将输入流读取位置跳到bmp文件尾
3、读取输入流中剩下的内容,写入到其它文件中即可

这种实现方式的关键在于解析bmp格式中记录的bmp文件的大小,其它什么都不需要获取,数据的隐藏性较差

重要源码:

  1. package com.pan.entity;
  2. /**
  3. * @author yp2
  4. * @date 2015-11-17
  5. * @description Bmp文件格式
  6. */
  7. public class Bmp {
  8. private BmpHeader bmpHeader;
  9. private BmpInfoHeader bmpInfoHeader;
  10. private BmpPalette bmpPalette;
  11. /**
  12. * bmp位图数据
  13. */
  14. private byte[] datas;
  15. public BmpHeader getBmpHeader() {
  16. return bmpHeader;
  17. }
  18. public void setBmpHeader(BmpHeader bmpHeader) {
  19. this.bmpHeader = bmpHeader;
  20. }
  21. public BmpInfoHeader getBmpInfoHeader() {
  22. return bmpInfoHeader;
  23. }
  24. public void setBmpInfoHeader(BmpInfoHeader bmpInfoHeader) {
  25. this.bmpInfoHeader = bmpInfoHeader;
  26. }
  27. public BmpPalette getBmpPalette() {
  28. return bmpPalette;
  29. }
  30. public void setBmpPalette(BmpPalette bmpPalette) {
  31. this.bmpPalette = bmpPalette;
  32. }
  33. public byte[] getDatas() {
  34. return datas;
  35. }
  36. public void setDatas(byte[] datas) {
  37. this.datas = datas;
  38. }
  39. }
  1. package com.pan.entity;
  2. /**
  3. * @author yp2
  4. * @date 2015-11-17
  5. * @description Bmp文件头部
  6. */
  7. public class BmpHeader {
  8. /**
  9. * 文件的类型,2个字节
  10. */
  11. private byte[] bfType;
  12. /**
  13. * 位图文件的大小,字节为单位,4个字节
  14. */
  15. private byte[] bfSize;
  16. /**
  17. * 保留,2个字节
  18. */
  19. private byte[] bfReserved1;
  20. /**
  21. * 保留,2个字节
  22. */
  23. private byte[] bfReserved2;
  24. /**
  25. * 说明从文件开始到实际的图像数据之间的字节的偏移量
  26. * 4个字节
  27. */
  28. private byte[] bfOffBits;
  29. public BmpHeader() {
  30. bfType = new byte[2];
  31. bfSize = new byte[4];
  32. bfReserved1 = new byte[2];
  33. bfReserved2 = new byte[2];
  34. bfOffBits = new byte[4];
  35. }
  36. public byte[] getBfType() {
  37. return bfType;
  38. }
  39. public void setBfType(byte[] bfType) {
  40. this.bfType = bfType;
  41. }
  42. public byte[] getBfSize() {
  43. return bfSize;
  44. }
  45. public void setBfSize(byte[] bfSize) {
  46. this.bfSize = bfSize;
  47. }
  48. public byte[] getBfReserved1() {
  49. return bfReserved1;
  50. }
  51. public void setBfReserved1(byte[] bfReserved1) {
  52. this.bfReserved1 = bfReserved1;
  53. }
  54. public byte[] getBfReserved2() {
  55. return bfReserved2;
  56. }
  57. public void setBfReserved2(byte[] bfReserved2) {
  58. this.bfReserved2 = bfReserved2;
  59. }
  60. public byte[] getBfOffBits() {
  61. return bfOffBits;
  62. }
  63. public void setBfOffBits(byte[] bfOffBits) {
  64. this.bfOffBits = bfOffBits;
  65. }
  66. }
  1. package com.pan.entity;
  2. /**
  3. * @author yp2
  4. * @date 2015-11-17
  5. * @description Bmp文件信息头部
  6. */
  7. public class BmpInfoHeader {
  8. /**
  9. * 位图信息头部所需要的字数,4个字节
  10. */
  11. private byte[] biSize;
  12. /**
  13. * 图像的宽度,像素为单位,4个字节
  14. */
  15. private byte[] biWidth;
  16. /**
  17. * 图像的高度,像素为单位,4个字节
  18. */
  19. private byte[] biHeight;
  20. /**
  21. * 为目标设备说明颜色平面数,其值将总是设为1,2个字节
  22. */
  23. private byte[] biPlans;
  24. /**
  25. * 说明比特数/像素,其值为1、4、8、16、24、32,2个字节
  26. */
  27. private byte[] biBitCount;
  28. /**
  29. * 说明图像数据压缩的类型,0 不压缩,4个字节
  30. */
  31. private byte[] biCompression;
  32. /**
  33. * 说明图像的大小,字节为单位,当压缩格式为0时,可设置为0,4个字节
  34. */
  35. private byte[] biSizeImage;
  36. /**
  37. * 说明水平分辨率,像素/米表示,有符号整数,4个字节
  38. */
  39. private byte[] biXPelsPerMeter;
  40. /**
  41. * 说明垂直分辨率,像素/米表示,有符号整数,4个字节
  42. */
  43. private byte[] biYPelsPerMeter;
  44. /**
  45. * 说明位图实际使用的彩色表中的颜色索引数,4个字节
  46. */
  47. private byte[] biClrUsed;
  48. /**
  49. * 说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要
  50. * 4个字节
  51. */
  52. private byte[] biClrImportant;
  53. public BmpInfoHeader() {
  54. biSize = new byte[4];
  55. biWidth = new byte[4];
  56. biHeight = new byte[4];
  57. biPlans = new byte[2];
  58. biBitCount = new byte[2];
  59. biCompression = new byte[4];
  60. biSizeImage = new byte[4];
  61. biXPelsPerMeter = new byte[4];
  62. biYPelsPerMeter = new byte[4];
  63. biClrUsed = new byte[4];
  64. biClrImportant = new byte[4];
  65. }
  66. public byte[] getBiSize() {
  67. return biSize;
  68. }
  69. public void setBiSize(byte[] biSize) {
  70. this.biSize = biSize;
  71. }
  72. public byte[] getBiWidth() {
  73. return biWidth;
  74. }
  75. public void setBiWidth(byte[] biWidth) {
  76. this.biWidth = biWidth;
  77. }
  78. public byte[] getBiHeight() {
  79. return biHeight;
  80. }
  81. public void setBiHeight(byte[] biHeight) {
  82. this.biHeight = biHeight;
  83. }
  84. public byte[] getBiPlans() {
  85. return biPlans;
  86. }
  87. public void setBiPlans(byte[] biPlans) {
  88. this.biPlans = biPlans;
  89. }
  90. public byte[] getBiBitCount() {
  91. return biBitCount;
  92. }
  93. public void setBiBitCount(byte[] biBitCount) {
  94. this.biBitCount = biBitCount;
  95. }
  96. public byte[] getBiCompression() {
  97. return biCompression;
  98. }
  99. public void setBiCompression(byte[] biCompression) {
  100. this.biCompression = biCompression;
  101. }
  102. public byte[] getBiSizeImage() {
  103. return biSizeImage;
  104. }
  105. public void setBiSizeImage(byte[] biSizeImage) {
  106. this.biSizeImage = biSizeImage;
  107. }
  108. public byte[] getBiXPelsPerMeter() {
  109. return biXPelsPerMeter;
  110. }
  111. public void setBiXPelsPerMeter(byte[] biXPelsPerMeter) {
  112. this.biXPelsPerMeter = biXPelsPerMeter;
  113. }
  114. public byte[] getBiYPelsPerMeter() {
  115. return biYPelsPerMeter;
  116. }
  117. public void setBiYPelsPerMeter(byte[] biYPelsPerMeter) {
  118. this.biYPelsPerMeter = biYPelsPerMeter;
  119. }
  120. public byte[] getBiClrUsed() {
  121. return biClrUsed;
  122. }
  123. public void setBiClrUsed(byte[] biClrUsed) {
  124. this.biClrUsed = biClrUsed;
  125. }
  126. public byte[] getBiClrImportant() {
  127. return biClrImportant;
  128. }
  129. public void setBiClrImportant(byte[] biClrImportant) {
  130. this.biClrImportant = biClrImportant;
  131. }
  132. }
  1. package com.pan.entity;
  2. /**
  3. * @author yp2
  4. * @date 2015-11-17
  5. * @description Bmp调色板
  6. */
  7. public class BmpPalette {
  8. private byte[][] palettes;      //颜色索引映射表
  9. public byte[][] getPalettes() {
  10. return palettes;
  11. }
  12. public void setPalettes(byte[][] palettes) {
  13. this.palettes = palettes;
  14. }
  15. }
  1. package com.pan.utils;
  2. /**
  3. * @author yp2
  4. * @date 2015-11-18
  5. * @description 字节操作工具
  6. */
  7. public class ByteUtil {
  8. /**
  9. * 将byte数组转换为16进制字符串
  10. * <br/>
  11. * 实现思路:
  12. * 先将byte转换成int,再使用Integer.toHexString(int)
  13. * @param data  byte数组
  14. * @return
  15. */
  16. public static String byteToHex(byte[] data, int start, int end) {
  17. StringBuilder builder = new StringBuilder();
  18. for(int i = start; i < end; i++) {
  19. int tmp = data[i] & 0xff;
  20. String hv = Integer.toHexString(tmp);
  21. if(hv.length() < 2) {
  22. builder.append("0");
  23. }
  24. builder.append(hv);
  25. /*builder.append(" ");*/
  26. if(i % 16 == 15) {
  27. /*builder.append("\n");*/
  28. }
  29. }
  30. return builder.toString();
  31. }
  32. /**
  33. * 将byte数组转换为16进制字符串(该字符串方便查看)
  34. * 输出信息版:16个字节一行显示
  35. * @param data
  36. * @param start
  37. * @param end
  38. * @return
  39. */
  40. public static String byteToHexforPrint(byte[] data, int start, int end) {
  41. StringBuilder builder = new StringBuilder();
  42. for(int i = start; i < end; i++) {
  43. int tmp = data[i] & 0xff;
  44. String hv = Integer.toHexString(tmp);
  45. if(hv.length() < 2) {
  46. builder.append("0");
  47. }
  48. builder.append(hv);
  49. builder.append(" ");
  50. if(i % 16 == 15) {
  51. builder.append("\n");
  52. }
  53. }
  54. return builder.toString();
  55. }
  56. /**
  57. * 十六进制字符串转换为字节数组
  58. * @param hexStr    十六进制字符串
  59. * @return          字节数组
  60. */
  61. public static byte[] hexToByte(String hexStr) {
  62. byte[] datas = new byte[(hexStr.length() - 1) / 2 + 1];
  63. hexStr = hexStr.toUpperCase();
  64. int pos = 0;
  65. for(int i = 0; i < hexStr.length(); i+=2) {
  66. if(i + 1 < hexStr.length()) {
  67. datas[pos] = (byte) ((indexOf(hexStr.charAt(i)+"") << 4) + indexOf(hexStr.charAt(i+1)+""));
  68. }
  69. pos++;
  70. }
  71. return datas;
  72. }
  73. /**
  74. * 计算指定字符串(这里要求是字符)的16进制所表示的数字
  75. * @param str
  76. * @return
  77. */
  78. public static int indexOf(String str) {
  79. return "0123456789ABCDEF".indexOf(str);
  80. }
  81. /**
  82. * 计算byte数组所表示的值,字节数组的值以小端表示,低位在低索引上,高位在高索引
  83. * <br/>
  84. * 例:data = {1,2},那么结果为: 2 << 8 + 1 = 513
  85. * @param data  byte数组
  86. * @return      计算出的值
  87. */
  88. public static long lowByteToLong(byte[] data) {
  89. long sum = 0;
  90. for(int i = 0; i < data.length; i++) {
  91. long value = ((data[i] & 0xff) << (8 * i));
  92. sum += value;
  93. }
  94. return sum;
  95. }
  96. /**
  97. * 计算byte数组所表示的值,字节数组的值以大端表示,低位在高索引上,高位在低索引
  98. * <br/>
  99. * 例:data = {1,2},那么结果为: 1 << 8 + 2 = 258
  100. * @param data  byte数组
  101. * @return      计算出的值
  102. */
  103. public static long highByteToLong(byte[] data) {
  104. long sum = 0;
  105. for(int i = 0; i < data.length; i++) {
  106. long value = ((data[i] & 0xff) << (8 * (data.length - i - 1)));
  107. sum += value;
  108. }
  109. return sum;
  110. }
  111. /**
  112. * 计算byte数组所表示的值,字节数组的值以小端表示,低位在低索引上,高位在高索引
  113. * <br/>
  114. * 例:data = {1,2},那么结果为: 2 << 8 + 1 = 513
  115. * @param data  byte数组
  116. * @return      计算出的值
  117. */
  118. public static int lowByteToInt(byte[] data) {
  119. int sum = 0;
  120. for(int i = 0; i < data.length; i++) {
  121. long value = ((data[i] & 0xff) << (8 * i));
  122. sum += value;
  123. }
  124. return sum;
  125. }
  126. /**
  127. * 计算byte数组所表示的值,字节数组的值以大端表示,低位在高索引上,高位在低索引
  128. * <br/>
  129. * 例:data = {1,2},那么结果为: 1 << 8 + 2 = 258
  130. * @param data  byte数组
  131. * @return      计算出的值
  132. */
  133. public static int highByteToInt(byte[] data) {
  134. int sum = 0;
  135. for(int i = 0; i < data.length; i++) {
  136. long value = ((data[i] & 0xff) << (8 * (data.length - i - 1)));
  137. sum += value;
  138. }
  139. return sum;
  140. }
  141. /**
  142. * long值转换为指定长度的小端字节数组
  143. * @param data      long值
  144. * @param len       长度
  145. * @return          字节数组,小端形式展示
  146. */
  147. public static byte[] longToLowByte(long data, int len) {
  148. byte[] value = new byte[len];
  149. for(int i = 0; i < len; i++) {
  150. value[i] = (byte) ((data >> (8 * i )) & 0xff);
  151. }
  152. return value;
  153. }
  154. /**
  155. * long值转换为指定长度的大端字节数组
  156. * @param data      long值
  157. * @param len       长度
  158. * @return          字节数组,大端形式展示
  159. */
  160. public static byte[] longToHighByte(long data, int len) {
  161. byte[] value = new byte[len];
  162. for(int i = 0; i < len; i++) {
  163. value[i] = (byte) ((data >> (8 * (len - 1 - i) )) & 0xff);
  164. }
  165. return value;
  166. }
  167. /**
  168. * int值转换为指定长度的小端字节数组
  169. * @param data      int值
  170. * @param len       长度
  171. * @return          字节数组,小端形式展示
  172. */
  173. public static byte[] intToLowByte(int data, int len) {
  174. byte[] value = new byte[len];
  175. for(int i = 0; i < len; i++) {
  176. value[i] = (byte) ((data >> (8 * i )) & 0xff);
  177. }
  178. return value;
  179. }
  180. /**
  181. * int值转换为指定长度的大端字节数组
  182. * @param data      int值
  183. * @param len       长度
  184. * @return          字节数组,大端形式展示
  185. */
  186. public static byte[] intToHighByte(int data, int len) {
  187. byte[] value = new byte[len];
  188. for(int i = 0; i < len; i++) {
  189. value[i] = (byte) ((data >> (8 * (len - 1 - i) )) & 0xff);
  190. }
  191. return value;
  192. }
  193. /**
  194. * 计算base的exponent次方
  195. * @param base      基数
  196. * @param exponent  指数
  197. * @return
  198. */
  199. public static long power(int base, int exponent) {
  200. long sum = 1;
  201. for(int i = 0; i < exponent; i++) {
  202. sum *= base;
  203. }
  204. return sum;
  205. }
  206. public static void main(String[] args) {
  207. byte[] data = new byte[]{1,2};
  208. System.out.println(highByteToInt(data));
  209. System.out.println(lowByteToInt(data));
  210. System.out.println(byteToHex(intToHighByte(258, 4), 0, 4));
  211. System.out.println(byteToHex(intToLowByte(258, 4), 0, 4));
  212. }
  213. }
  1. package com.pan.utils;
  2. import java.io.BufferedInputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileOutputStream;
  6. import java.io.IOException;
  7. import java.io.InputStream;
  8. import java.io.OutputStream;
  9. import com.pan.entity.Bmp;
  10. import com.pan.entity.BmpHeader;
  11. import com.pan.entity.BmpInfoHeader;
  12. import com.pan.entity.BmpPalette;
  13. /**
  14. * @author yp2
  15. * @date 2015-11-18
  16. * @description 重构后的Bmp工具
  17. * <br/>
  18. * 主要做了几件事: <br/>
  19. * 1.位图数据可以不存储,在需要写入的时候再去读原文件的位图数据部分<br/>
  20. * 2.抽除掉重复功能的代码<br/>
  21. */
  22. public class BmpUtilRefactoring {
  23. /**
  24. * 读取指定bmp文件的信息到对象中
  25. * @param bmpFile       bmp文件路径
  26. * @return              代表Bmp文件信息的对象
  27. */
  28. private static Bmp readBmp(String bmpFile) {
  29. Bmp bmp = new Bmp();
  30. File file = new File(bmpFile);
  31. InputStream in = null;
  32. try {
  33. in = new BufferedInputStream(new FileInputStream(file));
  34. in.mark(0);
  35. readBmpHeader(bmp, in);
  36. long bfOffBits = ByteUtil.lowByteToLong(bmp.getBmpHeader().getBfOffBits());
  37. long biSize = ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiSize());
  38. long biBitCount = ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiBitCount());
  39. int index = (int) (14 + biSize);
  40. //重新定位到调色板
  41. in.reset();
  42. in.skip(index);
  43. if(bfOffBits - biSize - 14 == 0) {
  44. //没有调色板
  45. System.out.println(ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiBitCount()) + "位色无调色板");
  46. } else {
  47. //有调色板
  48. byte[][] palettes = new byte[(int) ByteUtil.power(2, (int) biBitCount)][4];
  49. for(int i = 0; i < palettes.length && index < bfOffBits; i++) {
  50. in.read(palettes[i], 0, palettes[i].length);
  51. index += palettes[i].length;
  52. }
  53. BmpPalette bmpPalette = new BmpPalette();
  54. bmpPalette.setPalettes(palettes);
  55. bmp.setBmpPalette(bmpPalette);
  56. }
  57. //记录bmp文件位图数据
  58. /*
  59. int len = -1;
  60. byte[] buf = new byte[1024];
  61. StringBuilder data = new StringBuilder();
  62. while((len = in.read(buf, 0, buf.length)) > 0) {
  63. data.append(ByteUtil.byteToHex(buf,0, len));
  64. }
  65. bmp.setDatas(ByteUtil.hexToByte(data.toString()));*/
  66. } catch (IOException e) {
  67. e.printStackTrace();
  68. } finally {
  69. try {
  70. in.close();
  71. } catch (IOException e) {
  72. e.printStackTrace();
  73. }
  74. }
  75. return bmp;
  76. }
  77. /**
  78. * 读取bmp文件输入流的头部信息到Bmp中的头部信息中,要求输入流处于文件的开头
  79. * @param bmp               Bmp对象
  80. * @param in                bmp文件输入流
  81. * @throws IOException
  82. */
  83. private static void readBmpHeader(Bmp bmp, InputStream in) throws IOException {
  84. BmpHeader bmpHeader = new BmpHeader();
  85. in.read(bmpHeader.getBfType(), 0, bmpHeader.getBfType().length);
  86. in.read(bmpHeader.getBfSize(), 0, bmpHeader.getBfSize().length);
  87. in.read(bmpHeader.getBfReserved1(), 0, bmpHeader.getBfReserved1().length);
  88. in.read(bmpHeader.getBfReserved2(), 0, bmpHeader.getBfReserved2().length);
  89. in.read(bmpHeader.getBfOffBits(), 0, bmpHeader.getBfOffBits().length);
  90. bmp.setBmpHeader(bmpHeader);
  91. BmpInfoHeader bmpInfoHeader = new BmpInfoHeader();
  92. in.read(bmpInfoHeader.getBiSize(), 0, bmpInfoHeader.getBiSize().length);
  93. in.read(bmpInfoHeader.getBiWidth(), 0, bmpInfoHeader.getBiWidth().length);
  94. in.read(bmpInfoHeader.getBiHeight(), 0, bmpInfoHeader.getBiHeight().length);
  95. in.read(bmpInfoHeader.getBiPlans(), 0, bmpInfoHeader.getBiPlans().length);
  96. in.read(bmpInfoHeader.getBiBitCount(), 0, bmpInfoHeader.getBiBitCount().length);
  97. in.read(bmpInfoHeader.getBiCompression(), 0, bmpInfoHeader.getBiCompression().length);
  98. in.read(bmpInfoHeader.getBiSizeImage(), 0, bmpInfoHeader.getBiSizeImage().length);
  99. in.read(bmpInfoHeader.getBiXPelsPerMeter(), 0, bmpInfoHeader.getBiXPelsPerMeter().length);
  100. in.read(bmpInfoHeader.getBiYPelsPerMeter(), 0, bmpInfoHeader.getBiYPelsPerMeter().length);
  101. in.read(bmpInfoHeader.getBiClrUsed(), 0, bmpInfoHeader.getBiClrUsed().length);
  102. in.read(bmpInfoHeader.getBiClrImportant(), 0, bmpInfoHeader.getBiClrImportant().length);
  103. bmp.setBmpInfoHeader(bmpInfoHeader);
  104. }
  105. /**
  106. * 写入要隐藏文件的内容和原Bmp文件信息到指定数据文件中
  107. * @param bmp               原Bmp文件信息
  108. * @param inputFileName     要隐藏的文件
  109. * @param outFileName       输出的文件
  110. * @throws IOException
  111. */
  112. private static void writeFileToBmp(Bmp bmp, String bmpFileName, String inputFileName, String outFileName) throws IOException {
  113. File inputFile = new File(inputFileName);
  114. File outFile = new File(outFileName);
  115. File bmpFile = new File(bmpFileName);
  116. if(!outFile.exists()) {
  117. outFile.createNewFile();
  118. }
  119. //记录原来bmp文件的数据偏移位置
  120. long oldbfOffBits = ByteUtil.lowByteToLong(bmp.getBmpHeader().getBfOffBits());
  121. //计算出新的数据偏移位置:= 原来的偏移位置 + 要隐藏文件的总字节数
  122. long bfOffBits = inputFile.length() + ByteUtil.lowByteToLong(bmp.getBmpHeader().getBfOffBits());
  123. //设置新的数据偏移位置,以便写入新的文件中
  124. bmp.getBmpHeader().setBfOffBits(ByteUtil.longToLowByte(bfOffBits, 4));
  125. InputStream in = null;
  126. InputStream bmpIn = null;
  127. OutputStream out = null;
  128. try {
  129. in = new FileInputStream(inputFile);
  130. bmpIn = new BufferedInputStream(new FileInputStream(bmpFile));
  131. out = new FileOutputStream(outFile);
  132. //将bmp头部信息写入输入流中
  133. writeBmpHeader(bmp, out);
  134. //写入要隐藏的文件内容
  135. int len = -1;
  136. byte[] buf = new byte[1024];
  137. while((len = in.read(buf)) > 0) {
  138. out.write(buf, 0, len);
  139. }
  140. //跳过头部和调色板信息
  141. bmpIn.skip(oldbfOffBits);
  142. len = -1;
  143. //写入原有位图数据
  144. while((len = bmpIn.read(buf)) > 0) {
  145. out.write(buf, 0, len);
  146. }
  147. } catch (Exception e) {
  148. e.printStackTrace();
  149. } finally {
  150. try {
  151. in.close();
  152. out.close();
  153. bmpIn.close();
  154. } catch (IOException e) {
  155. e.printStackTrace();
  156. }
  157. }
  158. }
  159. /**
  160. * 将文件内容写入到指定的位图文件内,并改变输出文件名
  161. * @param bmpFileName       位图文件名
  162. * @param inputFileName     要隐藏的文件名
  163. * @param outFileName       输出文件名
  164. * @throws IOException
  165. */
  166. public static void writeFileToBmpFile(String bmpFileName, String inputFileName, String outFileName) throws IOException {
  167. Bmp bmp = readBmp(bmpFileName);
  168. writeFileToBmp(bmp, bmpFileName, inputFileName, outFileName);
  169. }
  170. /**
  171. * 读取bmp文件中隐藏的文件内容到指定的输出文件中去
  172. * @param bmpFileName       bmp文件名
  173. * @param outFileName       输出文件名
  174. * @throws IOException
  175. */
  176. public static void readFileFromBmpFile(String bmpFileName, String outFileName) throws IOException {
  177. File bmpFile = new File(bmpFileName);
  178. File outFile = new File(outFileName);
  179. Bmp bmp = new Bmp();
  180. if(!outFile.exists()) {
  181. outFile.createNewFile();
  182. }
  183. InputStream in = null;
  184. OutputStream out = null;
  185. int len = -1;
  186. try {
  187. in = new BufferedInputStream(new FileInputStream(bmpFile));
  188. out = new FileOutputStream(outFile);
  189. //标记当前输入流位置,方便后面reset跳转到当前输入流读取位置
  190. in.mark(0);
  191. //读取输入流中包含的头部信息
  192. readBmpHeader(bmp, in);
  193. //数据偏移位置
  194. long bfOffBits = ByteUtil.lowByteToLong(bmp.getBmpHeader().getBfOffBits());
  195. //使用的颜色索引数目
  196. long biClrUsed = ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiClrUsed());
  197. //位图信息头部字节数
  198. long biSize = ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiSize());
  199. //比特/像素
  200. long biBitCount = ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiBitCount());
  201. //重置到mark标记的位置,这里是跳转到输入流的开头
  202. in.reset();
  203. //保存当前文件输入流位置(字节位置)
  204. long sumLen = 0;
  205. if(biBitCount < 24) {
  206. if(biClrUsed == 0) {
  207. //索引全部都重要
  208. //跳过输入流中的54 + 调色板所占字节数 个字节,这样其实就跳转到了保存隐藏文件内容的位置
  209. in.skip(14 + biSize + ByteUtil.power(2, (int) biBitCount) * 4);
  210. sumLen = 14 + biSize + ByteUtil.power(2, (int) biBitCount) * 4;
  211. } else {
  212. //部分重要
  213. in.skip(14 + biSize + biClrUsed * 4);
  214. sumLen = 14 + biSize + biClrUsed * 4;
  215. }
  216. } else {
  217. //没有调色板
  218. in.skip(14 + biSize);
  219. sumLen = 14 + biSize;
  220. }
  221. byte[] buf = new byte[1024];
  222. while((len = in.read(buf)) > 0) {
  223. if((sumLen + len) > bfOffBits) {
  224. //如果超过了数据偏移位置,则截取剩余的字节进行保存
  225. out.write(buf, 0, (int) (bfOffBits - sumLen));
  226. break;
  227. } else {
  228. //没有超过数据偏移位置,则截取读取到的字节
  229. out.write(buf, 0, len);
  230. }
  231. sumLen += len;
  232. }
  233. } catch (Exception e) {
  234. e.printStackTrace();
  235. in.close();
  236. out.close();
  237. }
  238. }
  239. /**
  240. * 将bmp头部信息和调色板信息写入输入流中
  241. * @param out
  242. * @param bmp
  243. * @throws IOException
  244. */
  245. private static void writeBmpHeader(Bmp bmp, OutputStream out) throws IOException {
  246. BmpHeader bmpHeader = bmp.getBmpHeader();
  247. out.write(bmpHeader.getBfType());
  248. out.write(bmpHeader.getBfSize());
  249. out.write(bmpHeader.getBfReserved1());
  250. out.write(bmpHeader.getBfReserved2());
  251. out.write(bmpHeader.getBfOffBits());
  252. BmpInfoHeader bmpInfoHeader = bmp.getBmpInfoHeader();
  253. out.write(bmpInfoHeader.getBiSize());
  254. out.write(bmpInfoHeader.getBiWidth());
  255. out.write(bmpInfoHeader.getBiHeight());
  256. out.write(bmpInfoHeader.getBiPlans());
  257. out.write(bmpInfoHeader.getBiBitCount());
  258. out.write(bmpInfoHeader.getBiCompression());
  259. out.write(bmpInfoHeader.getBiSizeImage());
  260. out.write(bmpInfoHeader.getBiXPelsPerMeter());
  261. out.write(bmpInfoHeader.getBiYPelsPerMeter());
  262. out.write(bmpInfoHeader.getBiClrUsed());
  263. out.write(bmpInfoHeader.getBiClrImportant());
  264. BmpPalette bmpPalette = bmp.getBmpPalette();
  265. if(bmpPalette != null && bmpPalette.getPalettes() != null) {
  266. for(int i = 0; i < bmpPalette.getPalettes().length; i++) {
  267. out.write(bmpPalette.getPalettes()[i]);
  268. }
  269. }
  270. }
  271. }
  1. package com.pan.main;
  2. import java.io.IOException;
  3. import com.pan.utils.BmpUtilRefactoring;
  4. public class Main {
  5. public static void main(String[] args) throws IOException {
  6. /*1位色*/
  7. BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/SmallConfetti.bmp").getPath(),
  8. Main.class.getClassLoader().getResource("resource/SmallConfettiscrect.txt").getPath(),
  9. Main.class.getClassLoader().getResource("resource/").getPath() + "SmallConfettiout.bmp");
  10. BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "SmallConfettiout.bmp",
  11. Main.class.getClassLoader().getResource("resource/").getPath() + "SmallConfettiscrectout.txt");
  12. /*4位色*/
  13. BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/verisign.bmp").getPath(),
  14. Main.class.getClassLoader().getResource("resource/verisignscrect.txt").getPath(),
  15. Main.class.getClassLoader().getResource("resource/").getPath() + "verisignout.bmp");
  16. BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "verisignout.bmp",
  17. Main.class.getClassLoader().getResource("resource/").getPath() + "verisignscrectout.txt");
  18. BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/srun.bmp").getPath(),
  19. Main.class.getClassLoader().getResource("resource/srunscrect.txt").getPath(),
  20. Main.class.getClassLoader().getResource("resource/").getPath() + "srunout.bmp");
  21. BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "srunout.bmp",
  22. Main.class.getClassLoader().getResource("resource/").getPath() + "srunscrectout.txt");
  23. /*8位色*/
  24. BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/SplashScreen.bmp").getPath(),
  25. Main.class.getClassLoader().getResource("resource/SplashScreenscrect.txt").getPath(),
  26. Main.class.getClassLoader().getResource("resource/").getPath() + "SplashScreenout.bmp");
  27. BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "SplashScreenout.bmp",
  28. Main.class.getClassLoader().getResource("resource/").getPath() + "SplashScreenscrectout.txt");
  29. /*24位色*/
  30. BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/background.bmp").getPath(),
  31. Main.class.getClassLoader().getResource("resource/backgroundscrect.txt").getPath(),
  32. Main.class.getClassLoader().getResource("resource/").getPath() + "backgroundout.bmp");
  33. BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "backgroundout.bmp",
  34. Main.class.getClassLoader().getResource("resource/").getPath() + "backgroundscrectout.txt");
  35. /*32位色*/
  36. BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/WindowsMail.bmp").getPath(),
  37. Main.class.getClassLoader().getResource("resource/WindowsMailscrect.txt").getPath(),
  38. Main.class.getClassLoader().getResource("resource/").getPath() + "WindowsMailout.bmp");
  39. BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "WindowsMailout.bmp",
  40. Main.class.getClassLoader().getResource("resource/").getPath() + "WindowsMailscrectout.txt");
  41. }
  42. }

代码下载:搓http://download.****.net/detail/u012009613/9280153

转载请注明出处,谢谢!