首页 > 技术文章 > 将文件内容隐藏在bmp位图中

csguo 2017-12-25 17:56 原文

首先要实现这个功能,你必须知道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文件的大小,其它什么都不需要获取,数据的隐藏性较差

 

重要源码:

 

 

[java] view plain copy
 
  1. package com.pan.entity;  
  2.   
  3. /** 
  4.  * @author yp2 
  5.  * @date 2015-11-17 
  6.  * @description Bmp文件格式 
  7.  */  
  8. public class Bmp {  
  9.       
  10.     private BmpHeader bmpHeader;  
  11.     private BmpInfoHeader bmpInfoHeader;  
  12.     private BmpPalette bmpPalette;  
  13.     /** 
  14.      * bmp位图数据 
  15.      */  
  16.     private byte[] datas;  
  17.     public BmpHeader getBmpHeader() {  
  18.         return bmpHeader;  
  19.     }  
  20.     public void setBmpHeader(BmpHeader bmpHeader) {  
  21.         this.bmpHeader = bmpHeader;  
  22.     }  
  23.     public BmpInfoHeader getBmpInfoHeader() {  
  24.         return bmpInfoHeader;  
  25.     }  
  26.     public void setBmpInfoHeader(BmpInfoHeader bmpInfoHeader) {  
  27.         this.bmpInfoHeader = bmpInfoHeader;  
  28.     }  
  29.     public BmpPalette getBmpPalette() {  
  30.         return bmpPalette;  
  31.     }  
  32.     public void setBmpPalette(BmpPalette bmpPalette) {  
  33.         this.bmpPalette = bmpPalette;  
  34.     }  
  35.     public byte[] getDatas() {  
  36.         return datas;  
  37.     }  
  38.     public void setDatas(byte[] datas) {  
  39.         this.datas = datas;  
  40.     }  
  41.   
  42. }  



 

 

[java] view plain copy
 
  1. package com.pan.entity;  
  2.   
  3. /** 
  4.  * @author yp2 
  5.  * @date 2015-11-17 
  6.  * @description Bmp文件头部 
  7.  */  
  8. public class BmpHeader {  
  9.       
  10.     /** 
  11.      * 文件的类型,2个字节 
  12.      */  
  13.     private byte[] bfType;    
  14.     /** 
  15.      * 位图文件的大小,字节为单位,4个字节 
  16.      */  
  17.     private byte[] bfSize;    
  18.     /** 
  19.      * 保留,2个字节 
  20.      */  
  21.     private byte[] bfReserved1;  
  22.     /** 
  23.      * 保留,2个字节 
  24.      */  
  25.     private byte[] bfReserved2;  
  26.     /** 
  27.      * 说明从文件开始到实际的图像数据之间的字节的偏移量 
  28.      * 4个字节 
  29.      */  
  30.     private byte[] bfOffBits;  
  31.     public BmpHeader() {  
  32.         bfType = new byte[2];  
  33.         bfSize = new byte[4];  
  34.         bfReserved1 = new byte[2];  
  35.         bfReserved2 = new byte[2];  
  36.         bfOffBits = new byte[4];  
  37.     }  
  38.       
  39.     public byte[] getBfType() {  
  40.         return bfType;  
  41.     }  
  42.     public void setBfType(byte[] bfType) {  
  43.         this.bfType = bfType;  
  44.     }  
  45.     public byte[] getBfSize() {  
  46.         return bfSize;  
  47.     }  
  48.     public void setBfSize(byte[] bfSize) {  
  49.         this.bfSize = bfSize;  
  50.     }  
  51.     public byte[] getBfReserved1() {  
  52.         return bfReserved1;  
  53.     }  
  54.     public void setBfReserved1(byte[] bfReserved1) {  
  55.         this.bfReserved1 = bfReserved1;  
  56.     }  
  57.     public byte[] getBfReserved2() {  
  58.         return bfReserved2;  
  59.     }  
  60.     public void setBfReserved2(byte[] bfReserved2) {  
  61.         this.bfReserved2 = bfReserved2;  
  62.     }  
  63.     public byte[] getBfOffBits() {  
  64.         return bfOffBits;  
  65.     }  
  66.     public void setBfOffBits(byte[] bfOffBits) {  
  67.         this.bfOffBits = bfOffBits;  
  68.     }  
  69.       
  70.       
  71. }  

 

[java] view plain copy
 
  1. package com.pan.entity;  
  2.   
  3. /** 
  4.  * @author yp2 
  5.  * @date 2015-11-17 
  6.  * @description Bmp文件信息头部 
  7.  */  
  8. public class BmpInfoHeader {  
  9.       
  10.     /** 
  11.      * 位图信息头部所需要的字数,4个字节 
  12.      */  
  13.     private byte[] biSize;  
  14.     /** 
  15.      * 图像的宽度,像素为单位,4个字节 
  16.      */  
  17.     private byte[] biWidth;  
  18.     /** 
  19.      * 图像的高度,像素为单位,4个字节 
  20.      */  
  21.     private byte[] biHeight;  
  22.     /** 
  23.      * 为目标设备说明颜色平面数,其值将总是设为1,2个字节 
  24.      */  
  25.     private byte[] biPlans;  
  26.     /** 
  27.      * 说明比特数/像素,其值为1、4、8、16、24、32,2个字节 
  28.      */  
  29.     private byte[] biBitCount;  
  30.     /** 
  31.      * 说明图像数据压缩的类型,0 不压缩,4个字节 
  32.      */  
  33.     private byte[] biCompression;  
  34.     /** 
  35.      * 说明图像的大小,字节为单位,当压缩格式为0时,可设置为0,4个字节 
  36.      */  
  37.     private byte[] biSizeImage;  
  38.     /** 
  39.      * 说明水平分辨率,像素/米表示,有符号整数,4个字节 
  40.      */  
  41.     private byte[] biXPelsPerMeter;  
  42.     /** 
  43.      * 说明垂直分辨率,像素/米表示,有符号整数,4个字节 
  44.      */  
  45.     private byte[] biYPelsPerMeter;  
  46.     /** 
  47.      * 说明位图实际使用的彩色表中的颜色索引数,4个字节 
  48.      */  
  49.     private byte[] biClrUsed;  
  50.     /** 
  51.      * 说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要 
  52.      * 4个字节 
  53.      */  
  54.     private byte[] biClrImportant;  
  55.     public BmpInfoHeader() {  
  56.         biSize = new byte[4];  
  57.         biWidth = new byte[4];  
  58.         biHeight = new byte[4];  
  59.         biPlans = new byte[2];  
  60.         biBitCount = new byte[2];  
  61.         biCompression = new byte[4];  
  62.         biSizeImage = new byte[4];  
  63.         biXPelsPerMeter = new byte[4];  
  64.         biYPelsPerMeter = new byte[4];  
  65.         biClrUsed = new byte[4];  
  66.         biClrImportant = new byte[4];  
  67.     }  
  68.     public byte[] getBiSize() {  
  69.         return biSize;  
  70.     }  
  71.     public void setBiSize(byte[] biSize) {  
  72.         this.biSize = biSize;  
  73.     }  
  74.     public byte[] getBiWidth() {  
  75.         return biWidth;  
  76.     }  
  77.     public void setBiWidth(byte[] biWidth) {  
  78.         this.biWidth = biWidth;  
  79.     }  
  80.     public byte[] getBiHeight() {  
  81.         return biHeight;  
  82.     }  
  83.     public void setBiHeight(byte[] biHeight) {  
  84.         this.biHeight = biHeight;  
  85.     }  
  86.     public byte[] getBiPlans() {  
  87.         return biPlans;  
  88.     }  
  89.     public void setBiPlans(byte[] biPlans) {  
  90.         this.biPlans = biPlans;  
  91.     }  
  92.     public byte[] getBiBitCount() {  
  93.         return biBitCount;  
  94.     }  
  95.     public void setBiBitCount(byte[] biBitCount) {  
  96.         this.biBitCount = biBitCount;  
  97.     }  
  98.     public byte[] getBiCompression() {  
  99.         return biCompression;  
  100.     }  
  101.     public void setBiCompression(byte[] biCompression) {  
  102.         this.biCompression = biCompression;  
  103.     }  
  104.     public byte[] getBiSizeImage() {  
  105.         return biSizeImage;  
  106.     }  
  107.     public void setBiSizeImage(byte[] biSizeImage) {  
  108.         this.biSizeImage = biSizeImage;  
  109.     }  
  110.     public byte[] getBiXPelsPerMeter() {  
  111.         return biXPelsPerMeter;  
  112.     }  
  113.     public void setBiXPelsPerMeter(byte[] biXPelsPerMeter) {  
  114.         this.biXPelsPerMeter = biXPelsPerMeter;  
  115.     }  
  116.     public byte[] getBiYPelsPerMeter() {  
  117.         return biYPelsPerMeter;  
  118.     }  
  119.     public void setBiYPelsPerMeter(byte[] biYPelsPerMeter) {  
  120.         this.biYPelsPerMeter = biYPelsPerMeter;  
  121.     }  
  122.     public byte[] getBiClrUsed() {  
  123.         return biClrUsed;  
  124.     }  
  125.     public void setBiClrUsed(byte[] biClrUsed) {  
  126.         this.biClrUsed = biClrUsed;  
  127.     }  
  128.     public byte[] getBiClrImportant() {  
  129.         return biClrImportant;  
  130.     }  
  131.     public void setBiClrImportant(byte[] biClrImportant) {  
  132.         this.biClrImportant = biClrImportant;  
  133.     }  
  134.   
  135. }  

 

[java] view plain copy
 
  1. package com.pan.entity;  
  2.   
  3. /** 
  4.  * @author yp2 
  5.  * @date 2015-11-17 
  6.  * @description Bmp调色板 
  7.  */  
  8. public class BmpPalette {  
  9.       
  10.     private byte[][] palettes;      //颜色索引映射表  
  11.   
  12.     public byte[][] getPalettes() {  
  13.         return palettes;  
  14.     }  
  15.   
  16.     public void setPalettes(byte[][] palettes) {  
  17.         this.palettes = palettes;  
  18.     }  
  19.       
  20.   
  21. }  



 

 

[java] view plain copy
 
  1. package com.pan.utils;  
  2.   
  3. /** 
  4.  * @author yp2 
  5.  * @date 2015-11-18 
  6.  * @description 字节操作工具 
  7.  */  
  8. public class ByteUtil {  
  9.   
  10.     /** 
  11.      * 将byte数组转换为16进制字符串 
  12.      * <br/> 
  13.      * 实现思路: 
  14.      * 先将byte转换成int,再使用Integer.toHexString(int) 
  15.      * @param data  byte数组 
  16.      * @return 
  17.      */  
  18.     public static String byteToHex(byte[] data, int start, int end) {  
  19.         StringBuilder builder = new StringBuilder();  
  20.         for(int i = start; i < end; i++) {  
  21.             int tmp = data[i] & 0xff;  
  22.             String hv = Integer.toHexString(tmp);  
  23.             if(hv.length() < 2) {  
  24.                 builder.append("0");  
  25.             }  
  26.             builder.append(hv);  
  27.             /*builder.append(" ");*/  
  28.             if(i % 16 == 15) {  
  29.                 /*builder.append("\n");*/  
  30.             }  
  31.         }  
  32.         return builder.toString();  
  33.     }  
  34.       
  35.     /** 
  36.      * 将byte数组转换为16进制字符串(该字符串方便查看) 
  37.      * 输出信息版:16个字节一行显示 
  38.      * @param data 
  39.      * @param start 
  40.      * @param end 
  41.      * @return 
  42.      */  
  43.     public static String byteToHexforPrint(byte[] data, int start, int end) {  
  44.         StringBuilder builder = new StringBuilder();  
  45.         for(int i = start; i < end; i++) {  
  46.             int tmp = data[i] & 0xff;  
  47.             String hv = Integer.toHexString(tmp);  
  48.             if(hv.length() < 2) {  
  49.                 builder.append("0");  
  50.             }  
  51.             builder.append(hv);  
  52.             builder.append(" ");  
  53.             if(i % 16 == 15) {  
  54.                 builder.append("\n");  
  55.             }  
  56.         }  
  57.         return builder.toString();  
  58.     }  
  59.       
  60.     /** 
  61.      * 十六进制字符串转换为字节数组 
  62.      * @param hexStr    十六进制字符串 
  63.      * @return          字节数组 
  64.      */  
  65.     public static byte[] hexToByte(String hexStr) {  
  66.         byte[] datas = new byte[(hexStr.length() - 1) / 2 + 1];  
  67.         hexStr = hexStr.toUpperCase();  
  68.         int pos = 0;  
  69.         for(int i = 0; i < hexStr.length(); i+=2) {  
  70.             if(i + 1 < hexStr.length()) {  
  71.                 datas[pos] = (byte) ((indexOf(hexStr.charAt(i)+"") << 4) + indexOf(hexStr.charAt(i+1)+""));  
  72.             }  
  73.             pos++;  
  74.         }  
  75.         return datas;  
  76.     }  
  77.       
  78.     /** 
  79.      * 计算指定字符串(这里要求是字符)的16进制所表示的数字 
  80.      * @param str 
  81.      * @return 
  82.      */  
  83.     public static int indexOf(String str) {  
  84.         return "0123456789ABCDEF".indexOf(str);  
  85.     }  
  86.       
  87.     /** 
  88.      * 计算byte数组所表示的值,字节数组的值以小端表示,低位在低索引上,高位在高索引 
  89.      * <br/> 
  90.      * 例:data = {1,2},那么结果为: 2 << 8 + 1 = 513 
  91.      * @param data  byte数组 
  92.      * @return      计算出的值 
  93.      */  
  94.     public static long lowByteToLong(byte[] data) {  
  95.         long sum = 0;  
  96.         for(int i = 0; i < data.length; i++) {  
  97.             long value = ((data[i] & 0xff) << (8 * i));  
  98.             sum += value;  
  99.         }  
  100.         return sum;  
  101.     }  
  102.       
  103.     /** 
  104.      * 计算byte数组所表示的值,字节数组的值以大端表示,低位在高索引上,高位在低索引 
  105.      * <br/> 
  106.      * 例:data = {1,2},那么结果为: 1 << 8 + 2 = 258 
  107.      * @param data  byte数组 
  108.      * @return      计算出的值 
  109.      */  
  110.     public static long highByteToLong(byte[] data) {  
  111.         long sum = 0;  
  112.         for(int i = 0; i < data.length; i++) {  
  113.             long value = ((data[i] & 0xff) << (8 * (data.length - i - 1)));  
  114.             sum += value;  
  115.         }  
  116.         return sum;  
  117.     }  
  118.       
  119.     /** 
  120.      * 计算byte数组所表示的值,字节数组的值以小端表示,低位在低索引上,高位在高索引 
  121.      * <br/> 
  122.      * 例:data = {1,2},那么结果为: 2 << 8 + 1 = 513 
  123.      * @param data  byte数组 
  124.      * @return      计算出的值 
  125.      */  
  126.     public static int lowByteToInt(byte[] data) {  
  127.         int sum = 0;  
  128.         for(int i = 0; i < data.length; i++) {  
  129.             long value = ((data[i] & 0xff) << (8 * i));  
  130.             sum += value;  
  131.         }  
  132.         return sum;  
  133.     }  
  134.       
  135.     /** 
  136.      * 计算byte数组所表示的值,字节数组的值以大端表示,低位在高索引上,高位在低索引 
  137.      * <br/> 
  138.      * 例:data = {1,2},那么结果为: 1 << 8 + 2 = 258 
  139.      * @param data  byte数组 
  140.      * @return      计算出的值 
  141.      */  
  142.     public static int highByteToInt(byte[] data) {  
  143.         int sum = 0;  
  144.         for(int i = 0; i < data.length; i++) {  
  145.             long value = ((data[i] & 0xff) << (8 * (data.length - i - 1)));  
  146.             sum += value;  
  147.         }  
  148.         return sum;  
  149.     }  
  150.       
  151.     /** 
  152.      * long值转换为指定长度的小端字节数组 
  153.      * @param data      long值 
  154.      * @param len       长度 
  155.      * @return          字节数组,小端形式展示 
  156.      */  
  157.     public static byte[] longToLowByte(long data, int len) {  
  158.         byte[] value = new byte[len];  
  159.         for(int i = 0; i < len; i++) {  
  160.             value[i] = (byte) ((data >> (8 * i )) & 0xff);  
  161.         }  
  162.         return value;  
  163.     }  
  164.       
  165.     /** 
  166.      * long值转换为指定长度的大端字节数组 
  167.      * @param data      long值 
  168.      * @param len       长度 
  169.      * @return          字节数组,大端形式展示 
  170.      */  
  171.     public static byte[] longToHighByte(long data, int len) {  
  172.         byte[] value = new byte[len];  
  173.         for(int i = 0; i < len; i++) {  
  174.             value[i] = (byte) ((data >> (8 * (len - 1 - i) )) & 0xff);  
  175.         }  
  176.         return value;  
  177.     }  
  178.       
  179.     /** 
  180.      * int值转换为指定长度的小端字节数组 
  181.      * @param data      int值 
  182.      * @param len       长度 
  183.      * @return          字节数组,小端形式展示 
  184.      */  
  185.     public static byte[] intToLowByte(int data, int len) {  
  186.         byte[] value = new byte[len];  
  187.         for(int i = 0; i < len; i++) {  
  188.             value[i] = (byte) ((data >> (8 * i )) & 0xff);  
  189.         }  
  190.         return value;  
  191.     }  
  192.       
  193.     /** 
  194.      * int值转换为指定长度的大端字节数组 
  195.      * @param data      int值 
  196.      * @param len       长度 
  197.      * @return          字节数组,大端形式展示 
  198.      */  
  199.     public static byte[] intToHighByte(int data, int len) {  
  200.         byte[] value = new byte[len];  
  201.         for(int i = 0; i < len; i++) {  
  202.             value[i] = (byte) ((data >> (8 * (len - 1 - i) )) & 0xff);  
  203.         }  
  204.         return value;  
  205.     }  
  206.       
  207.     /** 
  208.      * 计算base的exponent次方 
  209.      * @param base      基数 
  210.      * @param exponent  指数 
  211.      * @return 
  212.      */  
  213.     public static long power(int base, int exponent) {  
  214.         long sum = 1;  
  215.         for(int i = 0; i < exponent; i++) {  
  216.             sum *= base;  
  217.         }  
  218.         return sum;  
  219.     }  
  220.       
  221.     public static void main(String[] args) {  
  222.         byte[] data = new byte[]{1,2};  
  223.         System.out.println(highByteToInt(data));  
  224.         System.out.println(lowByteToInt(data));  
  225.         System.out.println(byteToHex(intToHighByte(258, 4), 0, 4));  
  226.         System.out.println(byteToHex(intToLowByte(258, 4), 0, 4));  
  227.     }  
  228. }  



 

 

[java] view plain copy
 
  1. package com.pan.utils;  
  2.   
  3. import java.io.BufferedInputStream;  
  4. import java.io.File;  
  5. import java.io.FileInputStream;  
  6. import java.io.FileOutputStream;  
  7. import java.io.IOException;  
  8. import java.io.InputStream;  
  9. import java.io.OutputStream;  
  10.   
  11. import com.pan.entity.Bmp;  
  12. import com.pan.entity.BmpHeader;  
  13. import com.pan.entity.BmpInfoHeader;  
  14. import com.pan.entity.BmpPalette;  
  15.   
  16. /** 
  17.  * @author yp2 
  18.  * @date 2015-11-18 
  19.  * @description 重构后的Bmp工具 
  20.  * <br/> 
  21.  * 主要做了几件事: <br/> 
  22.  * 1.位图数据可以不存储,在需要写入的时候再去读原文件的位图数据部分<br/> 
  23.  * 2.抽除掉重复功能的代码<br/> 
  24.  */  
  25. public class BmpUtilRefactoring {  
  26.       
  27.     /** 
  28.      * 读取指定bmp文件的信息到对象中 
  29.      * @param bmpFile       bmp文件路径 
  30.      * @return              代表Bmp文件信息的对象 
  31.      */  
  32.     private static Bmp readBmp(String bmpFile) {  
  33.         Bmp bmp = new Bmp();  
  34.         File file = new File(bmpFile);  
  35.         InputStream in = null;  
  36.         try {  
  37.             in = new BufferedInputStream(new FileInputStream(file));  
  38.             in.mark(0);  
  39.             readBmpHeader(bmp, in);  
  40.               
  41.             long bfOffBits = ByteUtil.lowByteToLong(bmp.getBmpHeader().getBfOffBits());  
  42.             long biSize = ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiSize());  
  43.             long biBitCount = ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiBitCount());  
  44.             int index = (int) (14 + biSize);  
  45.             //重新定位到调色板  
  46.             in.reset();  
  47.             in.skip(index);  
  48.             if(bfOffBits - biSize - 14 == 0) {  
  49.                 //没有调色板  
  50.                 System.out.println(ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiBitCount()) + "位色无调色板");  
  51.             } else {  
  52.                 //有调色板  
  53.                 byte[][] palettes = new byte[(int) ByteUtil.power(2, (int) biBitCount)][4];  
  54.                 for(int i = 0; i < palettes.length && index < bfOffBits; i++) {  
  55.                     in.read(palettes[i], 0, palettes[i].length);  
  56.                     index += palettes[i].length;  
  57.                 }  
  58.                   
  59.                 BmpPalette bmpPalette = new BmpPalette();  
  60.                 bmpPalette.setPalettes(palettes);  
  61.                 bmp.setBmpPalette(bmpPalette);  
  62.             }  
  63.             //记录bmp文件位图数据  
  64.             /* 
  65.             int len = -1; 
  66.             byte[] buf = new byte[1024]; 
  67.             StringBuilder data = new StringBuilder(); 
  68.             while((len = in.read(buf, 0, buf.length)) > 0) { 
  69.                 data.append(ByteUtil.byteToHex(buf,0, len)); 
  70.             } 
  71.             bmp.setDatas(ByteUtil.hexToByte(data.toString()));*/  
  72.               
  73.               
  74.         } catch (IOException e) {  
  75.             e.printStackTrace();  
  76.         } finally {  
  77.             try {  
  78.                 in.close();  
  79.             } catch (IOException e) {  
  80.                 e.printStackTrace();  
  81.             }  
  82.         }  
  83.           
  84.         return bmp;  
  85.     }  
  86.       
  87.     /** 
  88.      * 读取bmp文件输入流的头部信息到Bmp中的头部信息中,要求输入流处于文件的开头 
  89.      * @param bmp               Bmp对象 
  90.      * @param in                bmp文件输入流 
  91.      * @throws IOException 
  92.      */  
  93.     private static void readBmpHeader(Bmp bmp, InputStream in) throws IOException {  
  94.         BmpHeader bmpHeader = new BmpHeader();  
  95.         in.read(bmpHeader.getBfType(), 0, bmpHeader.getBfType().length);  
  96.         in.read(bmpHeader.getBfSize(), 0, bmpHeader.getBfSize().length);  
  97.         in.read(bmpHeader.getBfReserved1(), 0, bmpHeader.getBfReserved1().length);  
  98.         in.read(bmpHeader.getBfReserved2(), 0, bmpHeader.getBfReserved2().length);  
  99.         in.read(bmpHeader.getBfOffBits(), 0, bmpHeader.getBfOffBits().length);  
  100.         bmp.setBmpHeader(bmpHeader);  
  101.           
  102.         BmpInfoHeader bmpInfoHeader = new BmpInfoHeader();  
  103.         in.read(bmpInfoHeader.getBiSize(), 0, bmpInfoHeader.getBiSize().length);  
  104.         in.read(bmpInfoHeader.getBiWidth(), 0, bmpInfoHeader.getBiWidth().length);  
  105.         in.read(bmpInfoHeader.getBiHeight(), 0, bmpInfoHeader.getBiHeight().length);  
  106.         in.read(bmpInfoHeader.getBiPlans(), 0, bmpInfoHeader.getBiPlans().length);  
  107.         in.read(bmpInfoHeader.getBiBitCount(), 0, bmpInfoHeader.getBiBitCount().length);  
  108.         in.read(bmpInfoHeader.getBiCompression(), 0, bmpInfoHeader.getBiCompression().length);  
  109.         in.read(bmpInfoHeader.getBiSizeImage(), 0, bmpInfoHeader.getBiSizeImage().length);  
  110.         in.read(bmpInfoHeader.getBiXPelsPerMeter(), 0, bmpInfoHeader.getBiXPelsPerMeter().length);  
  111.         in.read(bmpInfoHeader.getBiYPelsPerMeter(), 0, bmpInfoHeader.getBiYPelsPerMeter().length);  
  112.         in.read(bmpInfoHeader.getBiClrUsed(), 0, bmpInfoHeader.getBiClrUsed().length);  
  113.         in.read(bmpInfoHeader.getBiClrImportant(), 0, bmpInfoHeader.getBiClrImportant().length);  
  114.         bmp.setBmpInfoHeader(bmpInfoHeader);  
  115.     }  
  116.       
  117.     /** 
  118.      * 写入要隐藏文件的内容和原Bmp文件信息到指定数据文件中 
  119.      * @param bmp               原Bmp文件信息 
  120.      * @param inputFileName     要隐藏的文件 
  121.      * @param outFileName       输出的文件 
  122.      * @throws IOException  
  123.      */  
  124.     private static void writeFileToBmp(Bmp bmp, String bmpFileName, String inputFileName, String outFileName) throws IOException {  
  125.         File inputFile = new File(inputFileName);  
  126.         File outFile = new File(outFileName);  
  127.         File bmpFile = new File(bmpFileName);  
  128.         if(!outFile.exists()) {  
  129.             outFile.createNewFile();  
  130.         }  
  131.         //记录原来bmp文件的数据偏移位置  
  132.         long oldbfOffBits = ByteUtil.lowByteToLong(bmp.getBmpHeader().getBfOffBits());  
  133.         //计算出新的数据偏移位置:= 原来的偏移位置 + 要隐藏文件的总字节数   
  134.         long bfOffBits = inputFile.length() + ByteUtil.lowByteToLong(bmp.getBmpHeader().getBfOffBits());  
  135.         //设置新的数据偏移位置,以便写入新的文件中  
  136.         bmp.getBmpHeader().setBfOffBits(ByteUtil.longToLowByte(bfOffBits, 4));  
  137.           
  138.         InputStream in = null;  
  139.         InputStream bmpIn = null;  
  140.         OutputStream out = null;  
  141.           
  142.         try {  
  143.             in = new FileInputStream(inputFile);  
  144.             bmpIn = new BufferedInputStream(new FileInputStream(bmpFile));  
  145.             out = new FileOutputStream(outFile);  
  146.             //将bmp头部信息写入输入流中  
  147.             writeBmpHeader(bmp, out);  
  148.             //写入要隐藏的文件内容  
  149.             int len = -1;  
  150.             byte[] buf = new byte[1024];  
  151.             while((len = in.read(buf)) > 0) {  
  152.                 out.write(buf, 0, len);  
  153.             }  
  154.             //跳过头部和调色板信息  
  155.             bmpIn.skip(oldbfOffBits);  
  156.             len = -1;  
  157.             //写入原有位图数据  
  158.             while((len = bmpIn.read(buf)) > 0) {  
  159.                 out.write(buf, 0, len);  
  160.             }  
  161.         } catch (Exception e) {  
  162.             e.printStackTrace();  
  163.         } finally {  
  164.             try {  
  165.                 in.close();  
  166.                 out.close();  
  167.                 bmpIn.close();  
  168.             } catch (IOException e) {  
  169.                 e.printStackTrace();  
  170.             }  
  171.         }  
  172.     }  
  173.       
  174.     /** 
  175.      * 将文件内容写入到指定的位图文件内,并改变输出文件名 
  176.      * @param bmpFileName       位图文件名 
  177.      * @param inputFileName     要隐藏的文件名 
  178.      * @param outFileName       输出文件名 
  179.      * @throws IOException 
  180.      */  
  181.     public static void writeFileToBmpFile(String bmpFileName, String inputFileName, String outFileName) throws IOException {  
  182.         Bmp bmp = readBmp(bmpFileName);  
  183.         writeFileToBmp(bmp, bmpFileName, inputFileName, outFileName);  
  184.     }  
  185.       
  186.     /** 
  187.      * 读取bmp文件中隐藏的文件内容到指定的输出文件中去 
  188.      * @param bmpFileName       bmp文件名       
  189.      * @param outFileName       输出文件名 
  190.      * @throws IOException 
  191.      */  
  192.     public static void readFileFromBmpFile(String bmpFileName, String outFileName) throws IOException {  
  193.         File bmpFile = new File(bmpFileName);  
  194.         File outFile = new File(outFileName);  
  195.         Bmp bmp = new Bmp();  
  196.         if(!outFile.exists()) {  
  197.             outFile.createNewFile();  
  198.         }  
  199.         InputStream in = null;  
  200.         OutputStream out = null;  
  201.         int len = -1;  
  202.         try {  
  203.             in = new BufferedInputStream(new FileInputStream(bmpFile));  
  204.             out = new FileOutputStream(outFile);  
  205.             //标记当前输入流位置,方便后面reset跳转到当前输入流读取位置  
  206.             in.mark(0);  
  207.             //读取输入流中包含的头部信息  
  208.             readBmpHeader(bmp, in);  
  209.             //数据偏移位置  
  210.             long bfOffBits = ByteUtil.lowByteToLong(bmp.getBmpHeader().getBfOffBits());  
  211.             //使用的颜色索引数目  
  212.             long biClrUsed = ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiClrUsed());  
  213.             //位图信息头部字节数  
  214.             long biSize = ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiSize());  
  215.             //比特/像素  
  216.             long biBitCount = ByteUtil.lowByteToLong(bmp.getBmpInfoHeader().getBiBitCount());  
  217.             //重置到mark标记的位置,这里是跳转到输入流的开头  
  218.             in.reset();  
  219.             //保存当前文件输入流位置(字节位置)  
  220.             long sumLen = 0;  
  221.             if(biBitCount < 24) {  
  222.                 if(biClrUsed == 0) {  
  223.                     //索引全部都重要  
  224.                     //跳过输入流中的54 + 调色板所占字节数 个字节,这样其实就跳转到了保存隐藏文件内容的位置  
  225.                     in.skip(14 + biSize + ByteUtil.power(2, (int) biBitCount) * 4);  
  226.                     sumLen = 14 + biSize + ByteUtil.power(2, (int) biBitCount) * 4;  
  227.                 } else {  
  228.                     //部分重要  
  229.                     in.skip(14 + biSize + biClrUsed * 4);  
  230.                     sumLen = 14 + biSize + biClrUsed * 4;  
  231.                 }  
  232.                   
  233.             } else {  
  234.                 //没有调色板  
  235.                 in.skip(14 + biSize);  
  236.                 sumLen = 14 + biSize;  
  237.             }  
  238.             byte[] buf = new byte[1024];  
  239.             while((len = in.read(buf)) > 0) {  
  240.                 if((sumLen + len) > bfOffBits) {  
  241.                     //如果超过了数据偏移位置,则截取剩余的字节进行保存  
  242.                     out.write(buf, 0, (int) (bfOffBits - sumLen));  
  243.                     break;  
  244.                 } else {  
  245.                     //没有超过数据偏移位置,则截取读取到的字节  
  246.                     out.write(buf, 0, len);  
  247.                 }  
  248.                 sumLen += len;  
  249.             }  
  250.         } catch (Exception e) {  
  251.             e.printStackTrace();  
  252.             in.close();  
  253.             out.close();  
  254.         }  
  255.     }  
  256.       
  257.     /** 
  258.      * 将bmp头部信息和调色板信息写入输入流中 
  259.      * @param out 
  260.      * @param bmp 
  261.      * @throws IOException 
  262.      */  
  263.     private static void writeBmpHeader(Bmp bmp, OutputStream out) throws IOException {  
  264.         BmpHeader bmpHeader = bmp.getBmpHeader();  
  265.         out.write(bmpHeader.getBfType());  
  266.         out.write(bmpHeader.getBfSize());  
  267.         out.write(bmpHeader.getBfReserved1());  
  268.         out.write(bmpHeader.getBfReserved2());  
  269.         out.write(bmpHeader.getBfOffBits());  
  270.           
  271.         BmpInfoHeader bmpInfoHeader = bmp.getBmpInfoHeader();  
  272.         out.write(bmpInfoHeader.getBiSize());  
  273.         out.write(bmpInfoHeader.getBiWidth());  
  274.         out.write(bmpInfoHeader.getBiHeight());  
  275.         out.write(bmpInfoHeader.getBiPlans());  
  276.         out.write(bmpInfoHeader.getBiBitCount());  
  277.         out.write(bmpInfoHeader.getBiCompression());  
  278.         out.write(bmpInfoHeader.getBiSizeImage());  
  279.         out.write(bmpInfoHeader.getBiXPelsPerMeter());  
  280.         out.write(bmpInfoHeader.getBiYPelsPerMeter());  
  281.         out.write(bmpInfoHeader.getBiClrUsed());  
  282.         out.write(bmpInfoHeader.getBiClrImportant());  
  283.           
  284.         BmpPalette bmpPalette = bmp.getBmpPalette();  
  285.         if(bmpPalette != null && bmpPalette.getPalettes() != null) {  
  286.             for(int i = 0; i < bmpPalette.getPalettes().length; i++) {  
  287.                 out.write(bmpPalette.getPalettes()[i]);  
  288.             }  
  289.         }  
  290.           
  291.     }  
  292.       
  293.       
  294.   
  295. }  

 

[java] view plain copy
 
  1. package com.pan.main;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import com.pan.utils.BmpUtilRefactoring;  
  6.   
  7. public class Main {  
  8.   
  9.     public static void main(String[] args) throws IOException {  
  10.           
  11.         /*1位色*/  
  12.         BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/SmallConfetti.bmp").getPath(),  
  13.                 Main.class.getClassLoader().getResource("resource/SmallConfettiscrect.txt").getPath(),  
  14.                 Main.class.getClassLoader().getResource("resource/").getPath() + "SmallConfettiout.bmp");  
  15.           
  16.         BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "SmallConfettiout.bmp",  
  17.                 Main.class.getClassLoader().getResource("resource/").getPath() + "SmallConfettiscrectout.txt");  
  18.       
  19.         /*4位色*/  
  20.         BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/verisign.bmp").getPath(),  
  21.                 Main.class.getClassLoader().getResource("resource/verisignscrect.txt").getPath(),  
  22.                 Main.class.getClassLoader().getResource("resource/").getPath() + "verisignout.bmp");  
  23.           
  24.         BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "verisignout.bmp",  
  25.                 Main.class.getClassLoader().getResource("resource/").getPath() + "verisignscrectout.txt");  
  26.       
  27.         BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/srun.bmp").getPath(),  
  28.                 Main.class.getClassLoader().getResource("resource/srunscrect.txt").getPath(),  
  29.                 Main.class.getClassLoader().getResource("resource/").getPath() + "srunout.bmp");  
  30.         BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "srunout.bmp",  
  31.                 Main.class.getClassLoader().getResource("resource/").getPath() + "srunscrectout.txt");  
  32.           
  33.         /*8位色*/  
  34.         BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/SplashScreen.bmp").getPath(),  
  35.                 Main.class.getClassLoader().getResource("resource/SplashScreenscrect.txt").getPath(),  
  36.                 Main.class.getClassLoader().getResource("resource/").getPath() + "SplashScreenout.bmp");  
  37.           
  38.         BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "SplashScreenout.bmp",  
  39.                 Main.class.getClassLoader().getResource("resource/").getPath() + "SplashScreenscrectout.txt");  
  40.           
  41.         /*24位色*/  
  42.         BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/background.bmp").getPath(),  
  43.                 Main.class.getClassLoader().getResource("resource/backgroundscrect.txt").getPath(),  
  44.                 Main.class.getClassLoader().getResource("resource/").getPath() + "backgroundout.bmp");  
  45.           
  46.         BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "backgroundout.bmp",  
  47.                 Main.class.getClassLoader().getResource("resource/").getPath() + "backgroundscrectout.txt");  
  48.           
  49.         /*32位色*/  
  50.         BmpUtilRefactoring.writeFileToBmpFile(Main.class.getClassLoader().getResource("resource/WindowsMail.bmp").getPath(),  
  51.                 Main.class.getClassLoader().getResource("resource/WindowsMailscrect.txt").getPath(),  
  52.                 Main.class.getClassLoader().getResource("resource/").getPath() + "WindowsMailout.bmp");  
  53.           
  54.         BmpUtilRefactoring.readFileFromBmpFile(Main.class.getClassLoader().getResource("resource/").getPath() + "WindowsMailout.bmp",  
  55.                 Main.class.getClassLoader().getResource("resource/").getPath() + "WindowsMailscrectout.txt");  
  56.           
  57.           
  58.     }  
  59.   
  60. }  


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

转载请注明出处,谢谢!

推荐阅读