首先要实现这个功能,你必须知道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. ( function(){…} )()和( function (){…} () )是两种立即执行函数

    函数声明:function fnName () {…};函数表达式 var fnName = function () {…};匿名函数:function () {}; fnName(); functi ...

  2. 安装配置python环境,并跑一个推荐系统的例子

    1.官网下载python2.7,安装完后,在环境变量Path中加上这个路径 在控制台输入python,出现版本信息,就成功了. 2.我使用的是 pycharm,注册后,在 把自己的python.exe ...

  3. Mybatis 中实体类的编写

    一个实体类对应一个数据表 一个属性对应一个字段 默认情况下类名和属性名都采用 “下划线转驼峰” 的命名方式.但具体采用什么样的命名方式并不重要(方式一致即可),在后面使用这些对象的时候,可以通过 re ...

  4. Change the default MySQL data directory with SELinux enabled

    转载:https://rmohan.com/?p=4605 Change the default MySQL data directory with SELinux enabled This is a ...

  5. FTP服务与配置

    FTP简介 网络文件共享服务主流的主要有三种,分别是ftp.nfs.samba. FTP是File Transfer Protocol(文件传输协议)的简称,用于internet上的控制文件的双向传输 ...

  6. JS基础-运算符-函数

    1.运算符  1.赋值运算符和扩展运算符    1.赋值运算符 =    2.扩展运算符      +=,-=,*=,/=,%=,^=....      ex:        a=a+b;--> ...

  7. Hiberbate注解

    JPA:出现后,所有的ORM框架都有@注解  ,在所有的ORM框架里面是通用的,因此一般是建议大家使用注解进行配置. 实体类一般都有唯一属性,普通属性,集合属性 如何体现ORM思想的? @Entity ...

  8. ssm中通过ajax或jquer的validate验证原密码与修改密码的正确性

    一.ajax 1. <script type="text/javascript"> //验证原密码1.ajax,正则 var ok1=false,ok2=false,o ...

  9. BASH 正则表达式和文本处理工具

    本节内容 1.  什么是正则 2.  grep 3.  sed 4.  awk 5.  其他补充 一  什么是正则 正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方 ...

  10. 【WPF】【UWP】借鉴 asp.net core 管道处理模型打造图片缓存控件 ImageEx

    在 Web 开发中,img 标签用来呈现图片,而且一般来说,浏览器是会对这些图片进行缓存的. 比如访问百度,我们可以发现,图片.脚本这种都是从缓存(内存缓存/磁盘缓存)中加载的,而不是再去访问一次百度 ...