2009年6月18日 星期四

將文字隱藏在圖片中

這兩天在研究如何將文字資訊隱藏在圖片中的技術,在 google 查了一些 "secure image sharing" 的資料,但是都沒有看到實作的方法(其實是沒有看到範例程式碼),最後,我參考 Wiki 上關於 Steganography 的介紹:

http://en.wikipedia.org/wiki/Steganography

寫了以下的類別:

Steganography.as


package idv.ben.steganography
{
import flash.display.BitmapData;
import flash.geom.Point;

public class Steganography
{
static private var __instance:Steganography;
static public function getInstance():Steganography{
if(__instance==null)
__instance = new Steganography();
return __instance;
}

private var __totalWidth:int;
private var __totalHeight:int;
private var __point:Point;

public function Steganography()
{
if(__instance!=null)
throw new Error("plz use Steganography.getInstance()");
}

public function encode(oBD:BitmapData, txt:String):BitmapData{
var txtLen:int = txt.length;
var txtLenLen:int = String(txtLen).length;

if(oBD.width * oBD.height < txtLen + 10)
return null;

var newBD:BitmapData = oBD.clone();
__totalWidth = newBD.width;
__totalHeight = newBD.height;
__point = null;

var c:String;
var p:Point;
var newPixel:uint;

//txtLenLen
c = String(txtLenLen);
p = getNextPoint();
newPixel = putCharIntoPixel(c, oBD.getPixel(p.x, p.y));
newBD.setPixel(p.x, p.y, newPixel);

//txtLen
for(var i:int=0; i<txtLenLen; i++){
c = String(txtLen).substr(i, 1);
p = getNextPoint();
newPixel = putCharIntoPixel(c, oBD.getPixel(p.x, p.y));
newBD.setPixel(p.x, p.y, newPixel);
}

//txt
for(var j:int=0; j<txtLen; j++){
c = txt.substr(j, 1);
p = getNextPoint();
newPixel = putCharIntoPixel(c, oBD.getPixel(p.x, p.y));
newBD.setPixel(p.x, p.y, newPixel);
}

return newBD;
}

public function decode(bd:BitmapData):String{
__totalWidth = bd.width;
__totalHeight = bd.height;
__point = null;

var txtLenLen:int;
var txtLen:int
var txt:String = "";

var p:Point;
var c:String;

//txtLenLen
if(bd.width * bd.height <= 0)return "";
p = getNextPoint();
c = getCharFromPixel(bd.getPixel(p.x, p.y));
txtLenLen = int(c);

//txtLen
if(bd.width * bd.height <= (1 + txtLenLen))return "";
var strTxtLen:String = "";
for(var i:int=0; i<txtLenLen; i++){
p = getNextPoint();
c = getCharFromPixel(bd.getPixel(p.x, p.y));
strTxtLen += c;
}
txtLen = int(strTxtLen);

//txt
if(bd.width * bd.height <= (1 + txtLenLen + txtLen))return "";
for(var j:int=0; j<txtLen; j++){
p = getNextPoint();
c = getCharFromPixel(bd.getPixel(p.x, p.y));
txt += c;
}

return txt;
}

private function getNextPoint():Point{
if(__point==null){
__point = new Point(0, 0);
}else{
if(__point.x < __totalWidth-1){
__point.x++;
}else{
__point.x = 0;
__point.y++;
}
}
return __point;
}

private function putCharIntoPixel(char:String, oPixel:uint):uint{
var r:uint = (oPixel >> 16) & 255;
var g:uint = (oPixel >> 8) & 255;
var b:uint = (oPixel >> 0) & 255;

var charCode:String = String(char.charCodeAt(0));
while(charCode.length<3)
charCode = "0" + charCode;

var dr:uint = uint(charCode.substr(0, 1));
var dg:uint = uint(charCode.substr(1, 1));
var db:uint = uint(charCode.substr(2, 1));

var r2:uint = ((r >> 4) << 4) | dr;
var g2:uint = ((g >> 4) << 4) | dg;
var b2:uint = ((b >> 4) << 4) | db;

return r2 << 16 | g2 << 8 | b2;
}

private function getCharFromPixel(pixel:uint):String{
var dr:uint = (pixel >> 16) & 15;
var dg:uint = (pixel >> 8) & 15;
var db:uint = (pixel >> 0) & 15;

var charCode:String = String(dr) + String(dg) + String(db);
var char:String = String.fromCharCode(int(charCode));
return char;
}
}
}


使用範例如下,載入一張圖,並將 "Hello World!!" 的字串給隱藏進去,產生出第二張看似一樣的圖,最後再由第二張圖的 BitmapData 取出該字串。

SteganographyAS3 .as


package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;

import idv.ben.steganography.Steganography;

public class SteganographyAS3 extends Sprite
{
[Embed(source='../asset/IMG_3641.jpg', mimeType='image/jpeg')]
public var Img:Class;

public function SteganographyAS3()
{
var bmp:Bitmap = new Img() as Bitmap;
bmp.scaleX = bmp.scaleY = 0.5;
this.addChild(bmp);

var msg:String = "hello world!!";
var newBD:BitmapData = Steganography.getInstance().encode(bmp.bitmapData, msg);
var newBMP:Bitmap = new Bitmap(newBD);
newBMP.x = bmp.x + bmp.width;
newBMP.scaleX = newBMP.scaleY = 0.5;
this.addChild(newBMP);

var msg2:String = Steganography.getInstance().decode(newBMP.bitmapData);
trace("decode=>", msg2);
}
}
}


執行結果,出現兩張圖,看起來沒甚麼不同,可能要將欲編入的文字資料放多一點,或許可以看到一些像素的不同吧:



將第二張圖(右邊)的 BitmapData 中隱藏的資料解出來後,trace 在 Flex Builder 的輸出視窗中:



==============================================================

[2009-06-19-13-00]

作了一個範例,完整程式碼:


package
{
import com.adobe.images.PNGEncoder;

import fl.containers.ScrollPane;
import fl.controls.Button;
import fl.controls.TextArea;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.net.FileFilter;
import flash.net.FileReference;
import flash.utils.ByteArray;
import flash.utils.setTimeout;

import idv.ben.steganography.Steganography;

public class Demo2 extends MovieClip
{
public var btnBrowse:Button;
public var btnEncode:Button;
public var btnSave:Button;
public var btnLoad:Button;
public var btnDecode:Button;

public var spImg:ScrollPane;
public var spImg2:ScrollPane;

public var txtInput:TextArea;
public var txtOutput:TextArea;

private var __fileReference:FileReference = new FileReference();
private var __fileReference2:FileReference = new FileReference();

public function Demo2()
{
btnBrowse.addEventListener(MouseEvent.CLICK, onBtnBrowseClick);
btnEncode.addEventListener(MouseEvent.CLICK, onBtnEncodeClick);
btnSave.addEventListener(MouseEvent.CLICK, onBtnSaveClick);
btnLoad.addEventListener(MouseEvent.CLICK, onBtnLoadClick);
btnDecode.addEventListener(MouseEvent.CLICK, onBtnDecodeClick);

__fileReference.addEventListener(Event.SELECT, onFileReferenceSelect);
__fileReference.addEventListener(Event.COMPLETE, onFileReferenceComplete);

__fileReference2.addEventListener(Event.SELECT, onFileReference2Select);
__fileReference2.addEventListener(Event.COMPLETE, onFileReference2Complete);
}

private function onBtnBrowseClick(e:MouseEvent):void{
trace("onBtnBrowseClick()");

var typeFilter:Array = [new FileFilter("Images(*.jpg;*.gif;*.png)", "*.jpg;*.gif;*.png")];
__fileReference.browse(typeFilter);
}

private function onBtnEncodeClick(e:MouseEvent):void{
trace("onBtnEncodeClick()");

var bd:BitmapData = getEncodedBitmapData(spImg);
if(bd == null)
return;

var txt:String = txtInput.text;

if(txt == "")
return;

var newBD:BitmapData = Steganography.getInstance().encode(bd, txt);
var newBMP:Bitmap = new Bitmap(newBD);
spImg2.source = newBMP;
setTimeout(spImg2.update, 200);
}

private function onBtnSaveClick(e:MouseEvent):void{
trace("onBtnSaveClick()");

var bd:BitmapData = getEncodedBitmapData(spImg2);
if(bd == null)
return;

var data:ByteArray = PNGEncoder.encode(bd);
var fr:FileReference = new FileReference();
fr.save(data, "EncodeImage.png");
}

private function onBtnLoadClick(e:MouseEvent):void{
trace("onBtnLoadClick()");

var typeFilter:Array = [new FileFilter("Images(*.jpg;*.gif;*.png)", "*.jpg;*.gif;*.png")];
__fileReference2.browse(typeFilter);
}

private function onBtnDecodeClick(e:MouseEvent):void{
trace("onBtnDecodeClick()");

var bd:BitmapData = getEncodedBitmapData(spImg2);
if(bd == null)
return;

var txt:String = Steganography.getInstance().decode(bd);
txtOutput.text = txt;
}

private function onFileReferenceSelect(e:Event):void{
trace("onFileReferenceSelect()");

__fileReference.load();
}

private function onFileReferenceComplete(e:Event):void{
trace("onFileReferenceComplete()");

var data:ByteArray = __fileReference.data;
var loader:Loader = new Loader();
loader.loadBytes(data);
spImg.source = loader;
setTimeout(spImg.update, 200);
}

private function onFileReference2Select(e:Event):void{
trace("onFileReference2Select()");

__fileReference2.load();
}

private function onFileReference2Complete(e:Event):void{
trace("onFileReference2Complete()");

var data:ByteArray = __fileReference2.data;
var loader:Loader = new Loader();
loader.loadBytes(data);
spImg2.source = loader;
setTimeout(spImg2.update, 200);
}

private function getEncodedBitmapData(sp:ScrollPane):BitmapData{
if(sp.content == null)
return null;

var bmp:Bitmap;

if(sp.content is Loader){
if(Loader(sp.content).content == null)
return null;

bmp = Bitmap(Loader(sp.content).content);
}else if(sp.content is Bitmap){
bmp = Bitmap(sp.content);
}else{
return null;
}

return bmp.bitmapData;
}
}
}


操作步驟:

加密:
1. 按下 Browse 按鈕,載入原始圖片到左上角的 ScrollPane 中。
2. 在右上角的文字框中輸入欲藏入的文字 (此版 不接受雙位元文字)。
3. 按下 Encode 按鈕,會將處理好後的圖片放到左下角的 ScrollPane 中。
4. 可按下 Save 按鈕將圖片存檔。

解密:
5. 按下 Load 按鈕,將內含資訊的圖片載入到左下角的 ScrollPane 中。
6. 按下 Decode 按鈕,將文字資訊從圖片中解出來到右下角的文字框中。

程式試用:







或到以下網址試用:

http://ben.rb.chang.googlepages.com/20090619_SteganographyAS3.html

沒有留言: