首先要实现这个功能,你必须知道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.csdn.net/detail/u012009613/9280153

转载请注明出处,谢谢!

将文件内容隐藏在bmp位图中的更多相关文章

  1. C.C++把整个文件内容读进一个buffer中

    原创文章,未经本人允许禁止转载. //C方式, 调用的函数繁多 //fopen,fseek,ftell,fseek,malloc,fread,fclose,free. void foo() { FIL ...

  2. 五种方式让你在java中读取properties文件内容不再是难题

    一.背景 最近,在项目开发的过程中,遇到需要在properties文件中定义一些自定义的变量,以供java程序动态的读取,修改变量,不再需要修改代码的问题.就借此机会把Spring+SpringMVC ...

  3. PHP读取文件内容的五种方式(转载)

    php读取文件内容的五种方式 分享下php读取文件内容的五种方法:好吧,写完后发现文件全部没有关闭.实际应用当中,请注意关闭 fclose($fp); php读取文件内容: -----第一种方法--- ...

  4. Java&Xml教程(七)使用JDOM修改XML文件内容

    JDOM提供了非常灵活的方式操作XML文件,使用JDOM非常简单而且代码简洁可读性强.前面我们学习了如何使用JDOM解析XML文件,本节介绍如何使用JDOM修改XML文件内容. 在这个教程中,我们准备 ...

  5. .NET CORE下最快比较两个文件内容是否相同的方法

    本文因为未考虑磁盘缓存, 结果不是很准确, 更严谨的结果请参看本博文的续集 最近项目有个需求,需要比较两个任意大小文件的内容是否相同,要求如下: 项目是.NET CORE,所以使用C#进行编写比较方法 ...

  6. linux函数深入探索——open函数打开文件是否将文件内容加载到内存空间

    转自:https://blog.csdn.net/qq_17019203/article/details/85051627 问题:open(2)函数打开文件是否将文件内容加载到内存空间 首先,文件打开 ...

  7. PHP读取文件内容的五种方式

    -----第一种方法-----fread()-------- <?php $file_path = "test.txt"; if(file_exists($file_path ...

  8. php 写入文件 读取文件内容

    1.写入文件 fopen("文件名.扩展名","操作方式") fwrite(读取的文件,"写入的文件"); fclose(打开的对象变量); ...

  9. 如何将打印内容转换为bmp位图文件

    bmp是一种与硬件设备无关的图像文件格式,使用非常广.它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BblP文件所占用的空间很大.BMP文件的图像深度可选lbit.4bit.8 ...

随机推荐

  1. 五子棋 AI(AIpha-beta算法)

    博弈树 下过五子棋的人都应该知道,越厉害的人,对棋面的预测程度越深.换句话讲,就是当你下完一步棋,我就能在我的脑海里假设把我所有可能下的地方都下一遍,然后考虑我下完之后你又会下在哪里,最后我根据每次预 ...

  2. NFS服务简介与配置

    NFS简介 NFS特点 NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源 在NFS的应用中, ...

  3. Python(五) 字典

  4. Python从入门到精通之First!

    Python的种类 Cpython Python的官方版本,使用C语言实现,使用最为广泛,CPython实现会将源文件(py文件)转换成字节码文件(pyc文件),然后运行在Python虚拟机上. Jy ...

  5. mui.fire()触发自定义事件

    导读:添加自定义事件监听操作和标准js事件监听类似,可直接通过window对象添加,通过mui.fire()方法可触发目标窗口的自定义事件. 监听自定义事件 添加自定义事件监听操作和标准js事件监听类 ...

  6. To handling editor letter

    一般崔稿信写法: Dear Editor: Sorry for disturbing you. We’re not sure if it is the right time to contact yo ...

  7. servlet跨域

    后台代码 package edu.nf.ch01.server; import javax.servlet.ServletException; import javax.servlet.annotat ...

  8. Do More With These Great Plugins for Windows Live Writer(old)

    This article is out of day,now we use open live wirter, but we don’t have so much works great plugin ...

  9. unable to load http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl

    问题:unable to load http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl 解决:yum -y inst ...

  10. C++数组,sort

    cmake_minimum_required(VERSION 3.5) project(Test) add_executable( te test.cpp ) test.cpp #include &l ...