资源描述
图像嵌入水印
可见图像数字水印是可以看见的数字水印,就是将数字水印直接嵌入或覆盖在图像上。可见图像数字水印分为透明和不透明两种:(1)不透明数字水印的目的是在图像中具有视觉突出效果,形成鲜明的标识,用于防止图像被其它非授权的商业用途的使用。(2)透明数字水印的特点是在图像中不太醒目,在保证图像质量的前提下,尽量使水印标识难以剔除,进而保护文件的所有权。
2.1 可见图像数字水印JAVA程序设计
2.1.1 图像文件像素的获取
定义BufferedImage类的对象oImage,文件File类的对象of(其输入参数为图像文件名,暂定为m1-1.jpg),将名为m1-1.jpg的图像文件通过对象of从磁盘读到内存,赋予变量oImage,由于是文件读取,所以使用了try结构。对应程序如下:
BufferedImage oImage = null;
File of = new File("m1-1.jpg");
try{
oImage = ImageIO.read(of);
} catch(IOException e){
e.printStackTrace();
}
我们在ImageAssistance类中把上面代码定义在下面这个方法BufferedImage getImage(String filename, String format) 中,这样我们在读取图像可以使用:
oImage = ImageAssistancel.getImage(“m1-1.jpg”,“jpg”);
因为jpg类的图像文件有多种格式,处理每一种格式的图像文件需要编写与其特点相对应的java处理程序。本书处理的图像文件全为数码照相机或通用扫描仪中得到图像文件,其文件类型为RGB格式。通过调用oImage.getType()方法可以取得jpg图像文件的类型(例如:返回值为5表示是RGB格式图像文件,返回值为10表示gray格式)。本书调用的RGB图像文件返回值为5,对应的测试程序语句为:
System.out.println(oImage.getType());
当确定得到的图像文件的格式为RGB格式后,根据RGB格式的特点,通过getRaster()方法得到图像的坐标高度和宽度,对应的程序语句为:
WritableRaster oRaster = oImage.getRaster();
//得到图像的宽度
int oWidth = oRaster.getWidth();
//得到图像的高度
int oHeight = oRaster.getHeight();
RGB格式图像文件每一个点的颜色由红、绿、兰三种颜色构成,即实际图像可为3层,分别为R,G,B层,因此分解后的文件象素是实际坐标高度和宽度的三倍。因为用getPixels方法读取像素时,结果存入的是一维数组,所以先定义一个长度3×oWidth
×oHeight的一维数组oPixels,通过getPixels方法得到图像的像素图像存储到oPixels中,读取坐标的范围是从(0,0)坐标开始宽oWidth,高oHeight,对应程序语句为:
int[] oPixels = new int[3 * oWidth * oHeight];
oRaster.getPixels(0, 0, oWidth, oHeight, oPixels);
2.1.2 图像文件三维像素矩阵
通过二维图像得到的一维数组getPixels中,图像文件每个坐标点分别由红、绿、兰3个像素构成,即getPixels数组的第1(红)、2(绿)、3(兰)个元素合成了第1个坐标点的颜色,4、5、6个元素合成了第2个坐标点的颜色。将getPixels数组中每隔3个元素抽取一个得到对应于二维图像文件的R(红)、G(绿)、B(兰)3个二维图像像素矩阵,其相互关系如图2.1。矩阵中每个像素的取值范围为0-255。二维数组行和列坐标的转换点是
一维数组getPixels,长度为3×oWidth×oHeight
A[0]
A[1]
A[2]
A[3]
A[4]
A[5]
A[6]
1
2
1
2
1
2
B像素矩阵 G像素矩阵 R像素矩阵
图2.1红绿兰数组
通过原图像文件宽oWidth,高oHeight数值得到的。最后通过3个二维矩阵合成1个代表图像文件的三维像素矩阵。在MathTool类中我们定义了一维数组转为二维数组的方法,在ImageAssistance类中定义了一维数组转换为三维数组的方法,一维数组转为二维数组对应程序语句为:
class MathTool {
static int[][] arrayToMatrix(int[] m,int width,int height){
int[][] result = new int[height][width];
for(int i=0;i<height;i++){
for(int j=0;j<width;j++){
int p = j * height + i;
result[i][j] = m[p];
}
}
return result;
}
}
一维数组转为三维数组对应程序语句为:
class ImageAssistance{
static int[][][] getRGBArrayToMatrix(int[] pixels, int width, int height){
int[][][] result = new int[3][height][width];
int[][] temp = new int[3][width * height];
for(int i=0;i<pixels.length;i++){
int m = i / 3;
int n = i % 3;
temp[n][m] = pixels[i];
}
result[0] = MathTool.arrayToMatrix
(temp[0],width,height);
result[1] = MathTool.arrayToMatrix
(temp[1],width,height);
result[2] = MathTool.arrayToMatrix
(temp[2],width,height);
return result;
}
}
将RGB格式图像文件的一维数组oPixels转换为三维像素矩阵oDPixels对应程序调用语句为:
int[][][] oDPixels = ImageAssistance.
getRGBArrayToMatrix(oPixels, oWidth, oHeight);
2.1.3 可见水印的嵌入
将图像三个层R,G,对应的三维矩阵坐标点(0,0)到(30,30)位置的像素值设为255-15,产生一种“不透明”的由白变黑的色彩方块明水印,对应程序语句:
for(int k=0;k<=2;k++){
for(int i=0;i<=30;i++){
for(int j=0,c=0;j<=30;j++,c=c+8){
oDPixels[k][i][j] = 255–c;
}
}
}
将图像0层即R层矩阵的坐标点(0,30)到(30,60)位置的像素值设为255-15,其它2层的像素图像不变,产生一种“半透明”的由红变绿的色彩方块明水印,对应程序语句:
for(int i=0;i<=30;i++){
for(int j=31,c=0;j<=60;j++,c=c+8)
oDPixels[0][i][j] = 255–c;
}
2.1.4 三维像素矩阵的逆变换
在MathTool类中定义了一个方法将二维数组转为一维数组,在ImageAssiatance类中定义了另一个方法将三维数组转为一维数组,把int型二维数组转为一维数组对应程序:
class MathTool{
public static int[] matrixToArray(int[][] m){
int p = m.length * m[0].length;
int[] result = new int[p];
for(int i=0;i<m.length;i++){
for(int j=0;j<m[i].length;j++){
int q = j * m.length + i;
result[q] = m[i][j];
}
}
return result;
}
}
把int型三维数组转为一维数组对应程序:
public class ImageAssistance {
public static int[] getRGBMatrixToArray(int[][][] m){
int width = m[0].length;
int height = m[0][0].length;
int len = width * height;
int[] result = new int[3*len];
int[][] temp = new int[3][len];
temp[0] = Ml.matrixToArray(m[0]);
temp[1] = Ml.matrixToArray(m[1]);
temp[2] = Ml.matrixToArray(m[2]);
for(int i=0;i<3;i++){
for(int j=0;j<temp[i].length;j++){
result[3 * j + i] = temp[i][j];
}
}
return result;
}
//其他代码省略
}
将RGB格式图像的三维矩阵oDPixels形式变为RGB格式的一维数组result,对应程序:
int[] result = ImageAssistance.getRGBMatrixToArray(oDPixels);
2.1.5 像素转换成图像文件
定义BufferedImage类的对象outImage,大小和原始图像一样大,格式为BufferedImage.TYPE_3BYTE_BGR,即RGB格式。对应语句:
BufferedImage outImage = new BufferedImage(oWidth, oHeight, BufferedImage.TYPE_3BYTE_BGR);
WritableRaster outRaster = outImage.getRaster();
按照第一节图像像素获取的逆过程将一维数组result转换为图像文件,取名为m1-2.jpg。setPixels是设置图像的像素,对应程序语句:
outRaster.setPixels(0, 0, oWidth, oHeight, result);
//图像文件的写入
try{
File outFile = new File("m1-2.jpg");
ImageIO.write(outImage,"jpeg",outFile);
}catch(IOException e){
e.printStackTrace();
}
在ImageAssistance类中可以把上面代码封装在方法void setImage(BufferedImage image,String filename, String
format)中,样在读取图像时,可以使用方法ImageAssistancel.
getImage(outImage,“m1-1.jpg”,“jpg”);
2.2 JPG-24BPP可见图像数字水印JAVA实现
2.2.1 创建新文件
JDK平台的安装和程序的运行
(1)安装jdk-6u5-windows-i586-p.exe;
(2)通过数码相机或扫描仪得图像文件(属性为24bpp)。
(3)图像尺寸通过画图板缩小到400×400象素以内,文件取名m2-01.jpg;
(4)编译、运行源程序m221.java,将文件m2-01.jpg读入后取名m2-02.jpg生成新文件再存盘。
JAVA试验用源程序
// 程序名 :m221.java
// 目的 :文件取存实验
// 编写时间:2008年9月26日
import java.io.*;
import javax.imageio.*;
import java.awt.image.*;
public class m221 {
public static void main(String [] args) {
try {
File inFile = new File("m2-01.jpg"); //输入文件
File outFile = new File("m2-02.jpg");//输出文件
//读取输入图像
BufferedImage inImage =
ImageIO.read(inFile);
//把图像写入磁盘文件
ImageIO.write(inImage, "jpeg", outFile);
}catch(IOException e) {
e.printStackTrace();
}
}
}//end of m221
输出结果(略)
2.2.2 嵌入可见水印
程序的运行:
(1)通过普通数码相机或扫描仪得图像文件(属性为24bpp)。
(2)图像尺寸通过画图板缩小到400×400象素以内,文件取名m2-01.jpg;
(3)编译、运行源程序m222.java;
(4)将文件m2-01上角嵌入可见方块水印,嵌入水印后生成的新文件取名为m2-03.jpg存盘。
JAVA试验用源程序
// 程序名 : m222.java
// 目的 : 用于可见水印演示实验
// 编写时间: 2008年9月26日
import java.awt.image.*;
import javax.imageio.*;
import java.io.*;
import java.util.*;
import javax.imageio.stream.*;
public class m222{
public static void main(String[] args){
m222 test = new m222();
test.start();
}
public void start(){
BufferedImage oImage = null;
File of = new File("m2-01.jpg");
//图像从磁盘读到内存,放在BufferedImage对象中
try{
oImage = ImageIO.read(of);
} catch(IOException e){
e.printStackTrace();
}
WritableRaster oRaster = oImage.getRaster();
int oWidth = oRaster.getWidth();//得图像宽度
int oHeight = oRaster.getHeight();//得图像高度
//定义长为3*oWidth*oHeight一维数组存储图像像素
int[] oPixels = new int[3 * oWidth * oHeight];
/*调WritableRaster的getPixels方法得到图像像素,从0,0坐标开始,行数是oWidth,列数是oHeight*/
oRaster.getPixels(0, 0, oWidth, oHeight, oPixels);
//调用图像辅助Image1类将向量变为三维矩阵
int[][][] oDPixels = MathTool.getRGBArrayToMatrix
(oPixels, oWidth, oHeight);
//图像三个层0,0到30,30位置像素设255-5
for(int k=0; k<=2; k++){
for(int i=0; i<=30; i++){
for(int j=0,c=0; j<=30; j++,c=c+8){
oDPixels[k][i][j] = 255 - c;
}
}
}
//0层0,30到30,60位置像素值设255-5,其它层不变
for(int i=0; i<=30; i++){
for(int j=31,c=0; j<=60; j++,c=c+8){
oDPixels[0][i][j] = 255 - c;
}
}
//三维矩阵变为一维向量
int[] result =
MathTool.getRGBMatrixToArray(oDPixels);
//定义BufferedImage和原图像大小一样,格式为RGB
BufferedImage outImage = new BufferedImage
(oWidth,oHeight, BufferedImage.TYPE_3BYTE_BGR);
WritableRaster outRaster = outImage.getRaster();
//setPixels是设置图像的像素,
outRaster.setPixels(0, 0, oWidth, oHeight, result);
try {
//图像写入
File outFile = new File("m2-03.jpg");
ImageIO.write(outImage,"jpeg",outFile);
System.out.println("ok,success!");
} catch(IOException e){
e.printStackTrace();
}
}
}
class MathTool{
//将RGB图像变3维数组,result[0],[1],[2]对应GRB三层
public static int[][][] getRGBArrayToMatrix
(int[] pixels, int width, int height) {
int[][][] result = new int[3][width][height];
int[][] temp = new int[3][width * height];
for(int i=0; i<pixels.length; i++){
int m = i / 3;
int n = i % 3;
temp [n][m] = pixels [i];
}
result[0] =
MathTool.arrayToMatrix(temp[0], width, height);
result[1] =
MathTool.arrayToMatrix(temp[1], width, height);
result[2] =
MathTool.arrayToMatrix(temp[2], width, height);
return result;
}
//将3维数组变为一维数组
public static int[] getRGBMatrixToArray(int[][][] m){
int width = m[0].length;
int height = m[0][0].length;
int len = width * height;
int[] result = new int[3 * len];
int[][] temp = new int[3][len];
temp[0] = MathTool.matrixToArray(m[0]);
temp[1] = MathTool.matrixToArray(m[1]);
temp[2] = MathTool.matrixToArray(m[2]);
for(int i=0;i<3; i++){
for(int j=0; j<temp[i].length; j++){
result[3 * j + i] = temp[i][j];
}
}
return result;
}
//把int型向量转为矩阵
public static int[][] arrayToMatrix(int[] m, int width,
int height){
int[][] result = new int [width][height];
for(int i=0; i<width; i++){
for(int j=0; j<height; j++){
int p = j * width + i;
result[i][j] = m[p];
}
}
return result;
}
//把int型矩阵转为向量
public static int[] matrixToArray(int[][] m){
int p = m.length * m[0].length;
int[] result = new int[p];
for(int i=0; i<m.length; i++){
for(int j=0; j<m[i].length; j++){
int q = j * m.length + i;
result[q] = m[i][j];
}
}
return result;
}
}//end
输出结果:
数字水印技术在手机领域的应用
随着通信技术的发展及手机的普及,人们正在不断地给手机增加新的功能。通过无线渠道对分发的数字产品进行版本控制,是DRM(数字版权保护)新的应用领域。由于J2ME是多数手机生产厂商都支持的平台。因此在未来的应用中,数字版权控制同J2ME结合有着天然的优势。
9.1 J2ME概述
因为接入网络的众多设备使用的操作系统各异,输入、输出方式和内存和处理功能各不相同,因此,Sun公司在发展Java网络应用并取得成功的同时,也面临着处理各种设备接入网络的挑战。为了解决这些问题,Sun公司推出了Java2平台的微型版,即J2ME(Java 2 Platform Micro Edition)。
J2ME栈结构如下图所示:
表9-1:J2ME栈结构
可选包(Optional Package)
简表(Profile)
配置(Configuration)
本地操作系统
下面分别介绍各层
1、配置层(Configuration)
配置是为一组“水平”设备分类所定义的最精简的公共平台,这些设配有着相近的处理器和内存容量。它定义了一类设备适用的JAVA虚拟机功能特性和Java类库最小的集合(核心类库)。不同配置的分类主要基于内存、显示、网络连接和处理能力方面的考虑。因为这些限制因素可能影响应用环境,因此,不能采用同样的Java虚拟机和核心类库,必须分别定义。J2ME根据消费电子设备最基本的特性把他们分为两类:一类是间断网络通信功能的个人移动设备,另一类是固定的不间断网络连接信息设备(如机顶盒,网络电视等)。配置层包括JVM(Java Virtual Machine)。
2、简表层 (Profile)
简表层位于配置层之上,它定义了某一设备家族API的最小集合,以保证设备间的协同工作。简表与配置的主要区别是配置必须满足所有的设备在内存、处理能力和连接方面的最低要求,而简表是针对用户界面,输入机制和数据持久性存储等设备。简表是某一特定配置上的实现,针对某一简表开发的应用程序可移植到支持这个简表的任何设备上。手机上通常会提供移动信息简表(Mobile Information Device Profile 即 MIDP)。
3、可选包(Optional Package)
可选包是位于简表之上并用来扩展简表的一套API。设计可选包是让这些API可以灵活的被加载在各种简表上。在为新特性开发API的时候,这些API在开始的时候以可选包方式出现。等到这些API逐渐成熟后,可选包再被集成到简表中。目前布的可选有:无线短消息收发、远程方法调用、安全保密、移动3D等。
9.2 J2ME安装与运行
9.2.1 J2ME开发环境安装与配置
1、安装JDK
安装JAVA开发环境jdk-1_5_0_04-windows-i586-p.ex到路径D:\Java\JDK\jdk1.6.0_05;
2、配置环境变量
我的电脑>属性>高级>环境变量>系统变量>设置环境变量:
path=d:\java\jdk\jdk1.6.0_05\bin;
java_home=D:\java\jdk\jdk1.6.0_05;(可省略)
path=%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin (可省略)
CLASSPATH=.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
(加.表示当前路径,%JAVA_HOME%引用前JAVA_HOME,可省略)
3、安装J2ME无线工具包WTK
将光盘中j2me_wireless_toolkit-2_2-windows.exe安装到路径D:\WTK22。在安装的过程中计算机会自动搜寻JDK的安装位置,并自动进行关联。
4、安装Eclipse
Eclipse是开发java的IDE。在光盘中有Eclipse,也可以在sun公司官方网站上下载。Eclipse不用安装,直接解压拷到本机目录:D:\Java\eclipse3.2。
5、配置Eclipse的J2ME环境,安装Eclipseme
Eclipseme是Eclipse的一个可选插件,在进行J2ME开发时,需要配置很多选项,过程比较繁琐,所以最好使用此插件。在本章对应的光盘中有Eclipsme。下面介绍它的配置(在光盘中的Eclipse已经配置好Eclipseme,不需要再配置了)。
双击Eclipse图标,第一次使用时将进入Eclipse欢迎界面,关掉它进入工作界面。在Eclipse菜单中,选择Help>Software Updates>Find and Install>Search for new features to install>New Local Site,在打开对话框中选Eclipseme插件Finish。安装后在窗口Windows >首选项Preferences出现J2ME。
6、配置Eclipse的WTK
Windows>Preferences中,点击J2ME的WTK Root,输入WTK安装目录(D:\WTK22),选J2ME下拉菜单Device Management选Import弹出对话框,在Specify search directory中选WTK的bin目录(D:\WTK22\bin),点击refresh,点击finish。(或Eclipse窗口>首选项>J2ME>Platform Components,右键单击对话框右侧Wireless Toolkit选Add Wireless Toolkit,选WTK安装目录)。
9.2.2 J2ME环境下的开发步骤
1、新建一个工程
打开Eclipse界面,选择File>New>other>J2ME>J2ME Midlet Suite,输入任意工程名称,点击完成,新建了一个工程。
2、建立一个应用程序类
在刚建立的工程名字上面右击选中other>j2me>J2ME Midlet>next,输入类的名称,然后点finish,就建立了一个类。
3、运行
在刚建立的类中写入自己的代码,然后在需要调试或运行的主类上点击右健,从右健菜单中选择Run as>Run,在弹出的对话框中双击Emulated j2me Midlet ,在右边的区域会出现一个配置界面,在里面的Executable中选中Midlet单选框,选中Search,在里面找到自己的主类。
4、打包
要生成可以在手机上运行的程序,必须对应用程序打包。打包方法如下:在需要打包的工程目录上点击右健菜单,选择j2me:create package。如提示错误,请关闭工程后再打开重新打包。
5、安装到手机
经过第四步打成包之后,这个包就可以在特定型号的手机上使用了。首先把打包生成的文件拷贝到手机的存储卡中,然后在手机中找到改文件,选中它就会提示你安装(有的手机不需要安装),安装成功之后就可以运行了。
9.3 MIDP编程
MIDlet是MIDP中的基本执行单元,意思是MIDP小应用程序,用来满足小型资源受限制设备的特殊要求,按照MIDP规范定义一个新的应用程序模型。MIDlet与Java2中引入的Applet类似,它不能单独的运行,必须运行在特定的环境中,或运行在一个很大的应用程序容器中。它运行在JVM之上,不完成任何特定的任务。程序开发者需要再编写小的应用程序完成具体的工作。
9.3.1 MIDlet生命周期
MIDlet运行在一个受应用管理系统控制(Application Management System-AMS)的环境内。
1、AMS用生命周期控制MIDlet状态
StartApp()方法
AMS调用StartApp()方法来获取MIDlet所需的资源,然后MIDlet将会处于活动(Active)状态。
PauseApp()方法
AMS调用PauseApp()方法来释放MIDlet持有的任何资源。如果MIDlet创建了对象,则将对象状态存储到持久性存储器中并把对象设置为Null。
DestroyApp()方法
AMS调用destroyApp()方法来保存MIDlet的状态并释放MIDlet持有的任何资源。
2、MIDlet的状态
用户启动MIDlet后,被启动的MIDlet将处于应用程序生命周期中的三个状态(暂停、活动和销毁)。
(1)活动状态:当MIDlet进入活动状态时,它将获得用于执行任务的所有资源。转移到活动状态之后所需的线程被启动。
(2)暂停状态:当MIDlet进入暂停状态时,释放所有持有的资源并停止活动的线程。
(3)销毁状态:当MIDlet进入销毁状态时,释放所有资源,停止正在进行的线程并保存持久性的数据。
3、控制MIDlet状态改变
MIDlet类提供三个方法允许MIDlet控制自己状态。
resumeRequest()方法
MIDlet调用这个方法表明它已经准备好,进入活动状态。
notify Paused()方法
该方法允许MIDlet主动暂停自己。
notifyDestroy()方法
该方法允许MIDlet主动销毁自己。必须注意:如果上述的三个通知方法被调用,那么AMS就不会再调用相应的生命周期方法。
9.3.2 J2ME常用的类和方法
Display类
每个MIDlet只能有一个Display对象,应用程序可以通过调用静态成员方法getDisplay获得对Display对象的引用。在MIDlet的运行过程中,无论何时调用getDisplay方法都会返回相同的对Display对象的引用。getDisplay方法的语法定义如下:
Public static Display getDisplay(MIDlet m),参数m用于指定返回那个MIDlet对象的Display对象引用。
List组件
List是一个列表框,也是Screen的子表,用于显示一系列的列表项。MIDP提供的构造方法:List(String title, int listType, string[] stringElements, Image[] imageElements)这个构造方法在创建列表时不仅可以指定标题和类型,还可以指定具体的列表选项以及列表选项的图标,参数listType列表项可以有3种类型,分别为IMPLICIT、EXCLUSIVE和MOLTIPLE。
命令按钮事件处理
命令按钮的主要功能就是处理用户的操作,当用户按下某个键时,跟这个键相关的命令按钮就会接收到这个按钮时间,如果这个命令按钮注册了监听器CommandListener,这个事件就被送到监听器进行处理。命令按钮的动作监听器为CommandListener接口,包含在javax.microeditio.lcdui包中。命令按钮监听器只有方法:Public void commandAction(Command c,Displayable d)。参数c是被按下的命令按钮,参数d是包含命令按钮c的Displayable显示对象。命令按钮监听器通过命令按钮所在Displayable对象的setCommand Listener方法设置,其语法定义为Public void setCommandListener(CommandListener comd)其中,参数comd是实现CommandListener接口的一个类实例。
设置绘图颜色
Paint方法用于绘制画布屏幕。Graphics类中封装了二维几何图形的绘制原语,采用了24位的颜色模型,分别用3个8位字节表示红色(Red)颜色
展开阅读全文