Chinaunix首页 | 论坛 | 博客
  • 博客访问: 589269
  • 博文数量: 752
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 5005
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:47
文章分类

全部博文(752)

文章存档

2011年(1)

2008年(751)

我的朋友

分类:

2008-10-13 16:48:00

PhotoShop插件开发之选区(Selection)

作者:

我们的程序里用到的图都是放在一张大图里的,所以就有一个文件记录每个小图是放在这张大图的什么地方,类似这个样子:

.
   图要是少了还好,多到几十、几百个这样的记录,每次要更新一个图都要找半天,尤其是界面大变的时候,几乎所有的小图的位置都变了,这样就要在PhotoShop里找到每一个小图,记下它的坐标,然后在写到配置文件中。要是偶尔做做也就忍了,可是这种不幸的事情经常发生,忍无可忍,觉得这种事情计算机应该可以胜任,它能干的事情,我们坚决不能替它干。仔细研究了几天,总算研究明白了PS的插件机制,可以实现先Ctrl+C一些坐标位置,然后在PS中选中这些区域。
  还是Adobe比较牛,我们辛辛苦苦帮它开发插件,它还要收费。现在的PS插件开发的SDK已经不免费下载了,还好在免费的互联网上还能找到早期版本的免费SDK,我找到的是6.0的,开发的插件可以在最新的PS CS2中使用。
  据官方文档声明,PS大概支持9种插件,比较常见的是Filter,俗称滤镜,一般用来实现一些特殊的图像处理算法,如边缘提取等,我感兴趣的是Select插件,看名字就像是和选区有关。插件的使用很简单,放到PS安装目录下的Plug-Ins目录下的相应类别下即可,比如滤镜就放在Plug-Ins\Filters下,扩展名是.8BF,选择插件放在Plug-Ins\Select下,扩展名为.8BS.PS启动时会搜索这个目录。
  PS的SDK带了很多插件的例子,你可以找你感兴趣的那个类别的插件例子看看,然后改改就可以了。我们先看看PS 6.0 SDK 带的Selection目录下的Selectorama这个例子。它演示了如何在当前的文档上选中感兴趣的区域,不过例子似乎稍微复杂了点儿。
PS的Windows下的插件一般是一个标准的dll,入口函数为PluginMain,原型是:
void PluginMain (const short selector,PISelectionParams *selectionParamBlock,long *data,short *result);
其中,selector是一个类型参数,说明本次调用的目的是什么,如果是常量"selectionSelectorAbout",说明需要显示一个关于对话框。在滤镜插件中,PluginMain会被调用多次,可以根据selector来决定具体做什么操作。

selectionParamBlock 是指向一个庞大的结构的指针,里面几乎有所有你需要的东西。比如,当前文档的大小可以通过

selectionParamBlock->documentInfo->bounds 

获取,如果想知道现在用户是否选择了一块区域,可以通过 selectionParamBlock->documentInfo->selection->bounds 来获取。
剩下的两个都是输出参数,可以用来存储句柄,返回错误等,暂时可以不用理会。
  在PluginMain函数中,会间接调用DoExecute这个函数,传递的参数叫globals,其实是把输入参数 selectionParamBlock 包装了一下,真正有用的还是

globals->selectionParamBlock

  在插件中,如果想从PS里读数据,需要一个叫做read port的东西,例子中使用了ReadFromWritePort这个宏来获取一个read port,这个我们暂时可以不用管它,接着向下看,会看到分配了三块缓冲区:sBuffer,dBuffer,rBuffer,如果transparency不空的话,还会分配一个mBuffer的缓冲区。我实际用到的只是sBuffer和dBuffer,其它两个高级的东东还没用到。接下来是调用 AccountChannel 计算需要处理的通道,一般会有R G B 三个通道。然后就是关键的 ApplyChannel 函数来完成实际的工作。
  这个函数的参数很多,不过你只要记住刚才提到的sBuffer和dBuffer就够了。sBuffer用来保存从当前的图像中读来的图像数据,dBuffer用来保存你的选区信息,和sBuffer一一对应,如果某个象素需要选中,直接赋值为255即可。原例中需要选择的部分赋值是原来图像的内容,经过实践发现这样会造成魔棒选区的特效,我用不着这个高级功能,所以就直接赋成255了,可以精确的按我的要求工作。在这个函数里,考虑到图像可能会比较大,一次读过来可能受不了,所以先用了两个循环,按64×64的块大小循环读取处理,我们就可以再来一次循环,对每个64×64块的每个象素处理,根据剪贴板里设定的选区信息,判断当前象素的位置是否在这个选区内,如果是,就把dBuffer中的相应位置置为255,否则就是0。详情请参阅代码,为了使程序流程清楚,代码做了适当的整理。

//=============================PluginMain Start======================
DLLExport MACPASCAL void PluginMain (const short selector,
				PISelectionParams *selectionParamBlock,
				long *data,short *result)
{
	//显示About对话框
	if (selector == selectionSelectorAbout)
	{
		DoAbout((AboutRecordPtr)selectionParamBlock);
	}
	else
	{ 
		static const FProc routineForSelector [] =
		{
			/* selectionSelectorAbout  DoAbout, */
			/* selectionSelectorExecute */DoExecute
		};
		
		Ptr globalPtr = NULL;// Pointer for global structure
		GPtr globals = NULL; // actual globals
		
		//包装selectionParamBlock到globals中,真正有用的还是globals->selectionParamBlock
		globalPtr = AllocateGlobals ((uint32)result,
			(uint32)selectionParamBlock,
			selectionParamBlock->handleProcs,
			sizeof(Globals),
			data,
			InitGlobals);
		
		if (globalPtr == NULL)
		{
			*result = memFullErr;return;
		}
		
		globals = (GPtr)globalPtr;
		
		//调用 DoExecute 函数
		if (selector > selectionSelectorAbout && selector <= selectionSelectorExecute)
			(routineForSelector[selector-1])(globals);
		else
			gResult = selectionBadParameters;
		
		if ((Handle)*data != NULL)
			PIUnlockHandle((Handle)*data);
		
	} // about selector special
	
}
//=============================PluginMain End=================================


//=============================DoExecute Start=================================
void DoExecute (GPtr globals)
{
	//一些变量声明,省略...
	//...
	//
	
	//从剪贴板中读取自己定义格式的选区信息,保存到全局变量中,我加的
	
	//省略部分内容
	gQueryForParameters = ReadScriptParams (globals);
	gStuff->treatment = 0;//KeyToEnum(EnumToKey(gCreate,typeMyCreate),typeMyPISel);//忽略原程序的UI参数处理
	
	//获取读取端口
	gResult = ReadFromWritePort(&selectionRead, selection->port);
	//省略部分内容
	
	//分配内存
	gResult = AllocateBuffer (kBufferSize, &sBuffer);
	if (gResult != noErr) goto CleanUp;
	
	gResult = AllocateBuffer (kBufferSize, &dBuffer);
	if (gResult != noErr) goto CleanUp;
	
	gResult = AllocateBuffer (kBufferSize, &rBuffer);
	if (gResult != noErr) goto CleanUp;
	sData = LockBuffer (sBuffer, false);
	dData = LockBuffer (dBuffer, false);
	rData = LockBuffer (rBuffer, false);
	
	//省略部分内容
	//统计要处理的通道
	curChannel = composite;
	while (curChannel != NULL)
	{
		if (DoTarget  curChannel->target : curChannel->shown)
			total += AccountChannel (curChannel, transparency, selection);
		
		curChannel = curChannel->next;
	}
	//进行实际的处理工作
	while (curChannel != NULL)
	{
		if (DoTarget  curChannel->target : curChannel->shown)
		{
			ApplyChannel (globals, curChannel, &sDesc,
				transparency, &mDesc,
				selection, selectionRead, &dDesc,
				&rDesc, &done, total);
			if (gResult != noErr) goto CleanUp;
		}
		curChannel = curChannel->next;
	}
	
	//善后工作...
}
//=============================DoExecute End=====================================
//=============================ApplyChannel Start==================================
static void ApplyChannel (GPtr globals,
			ReadChannelDesc *source,
			PixelMemoryDesc *sDesc,
			ReadChannelDesc *mask, 
			PixelMemoryDesc *mDesc,
			WriteChannelDesc *dest,
			ChannelReadPort destRead,
			PixelMemoryDesc *dDesc,
			PixelMemoryDesc *rDesc,
			int32 *done,int32 total)
{
	//声明变量,参数检查,省略
	//内层循环中,每次读取64×64的块处理
	//#define kBlockRows 64
	for (row = limit.top; row < limit.bottom; row += kBlockRows)
		for (col = limit.left; col < limit.right; col += kBlockCols)
		{
			//省略部分内容
			gResult = ReadPixels (destRead, &scaling, &area, dDesc, &wrote);
			//省略部分内容
			gResult = ReadPixels (source->port, &scaling, &area, sDesc, &wrote);
			s = (unsigned8 *) sDesc->data;//这里是原图象数据
			d = (unsigned8 *) dDesc->data;//这里保存处理结果
			//逐个象素处理64×64的块
			for (row2 = 0; row2 < kBlockRows; ++row2)
			{
				int y = row + row2;
				for (col2 = 0; col2 < kBlockCols; ++col2)
				{
					int x = col + col2;
					int nRc = 0;
					bool bFound = false;
					while(nRc < g_rcCount)//g_rcCount是一共要显示的区域数,通过剪贴板传递计算
					{
						if(PtInRect(&g_rcArr[nRc],x,y))//g_rcArr存放所有要显示的区域
						{
							*d = 255;//这个象素处于选区内
							bFound = true;
							break;
						}
						++nRc;
					}
					//if(!bFound) *d = 0;
					++s;
					++d;
					++r;
				}
			}
			//处理完毕一小块,写回
			gResult = WritePixels (dest->port, &area, dDesc);
			//省略部分内容
		}
		
		
}
//=============================ApplyChannel End======================================


--------------------next---------------------

谢谢楼主分享!
我现在刚刚接触photoshop插件开发,您的文章的代码是在原来的示例代码基础上进行修改,向您请教一下,如果从头开始做一个插件的话,就是在VC里面新建一个工程,不知道是怎么样建这个工程,最后这个photoshop插件文件的格式又是怎么控制的,帮我解答一下吧,谢谢! ( wendy2101 发表于 2007-5-21 16:39:00)
 
谢谢楼主的分享!
想请教楼主一个问题,photoshop的sdk能否用于开发独立的程序?即sdk里是否有像gdi一样的api?如我想写一个独立的程序用于图层合并,是否用photoshop的sdk的相关api就可以实现而不是只能在photoshop本身上用?谢谢! ( musicfan 发表于 2007-5-7 1:19:00)
 
关于编译不过的问题:
ps的每个工程包含了很多相对路径的头文件和资源文件包含,本例需要先下载安装ps的sdk,然后把代码解压到这个例子的路径下,覆盖原来的,比如解压到
C:\Program Files\Adobe\Adobe Photoshop 6.0 SDK\SampleCode\Selection\Selectorama\
( genghz 发表于 2007-4-16 9:13:00)
 
编译不过去啊 ( 0011411 发表于 2007-4-14 16:15:00)
 
.......................................................

--------------------next---------------------

阅读(242) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~