故事的背景:2016年,广州的冬天下起了鹅毛大雪。。。所以决定启动修改之前项目中的一个硬伤。如果各位程序员身边,有这么一位产品经理,用这么充分的理由来说服你,你会吐血三尺么?
硬伤:客户端软件处理图片速度很慢,显然不适应这个冬天的寒冷。
疗伤方案:既然中了寒毒,就需要对症下药,找到病根,AIR效率不高的事实又被端出了台面。
之前软件处理图片的逻辑都是托付给AIR端出里,当客户导入大量的高分辨率的大图时候,处理效果很慢,电脑配置差的,有时候会造成假死状态。项目软件是一个类似于电子相册的制作工具,可以可以上传大图,再DIY生产电子相册。抽离之前的代码逻辑,发现:
位图加载-------->AIR端----------->根据需求,处理bitmapdata数据---------->生产对应两个小尺寸的位图 ------->再用于DIY编辑。
上述就是程序基本逻辑,如果在flex内部优化的话,也没有很大的优化方案了,所以就有了下面提出的方案。
1.将上述处理逻辑给C++处理
C++的高效,不容置疑,这个方案肯定是行的通的,所以陷入了C++的研究中。
一.这里插播一个片段:
为了验证上述方案可研究型,用C#临时写了一个demo,实测确实效率高了很多,放出关键代码:
flash-AIR端代码
import flash.filesystem.File;
import flash.events.MouseEvent;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.Event;
import flash.display.BitmapData;
import flash.display.Bitmap;
import fl.motion.easing.Back;
import flash.utils.Timer;
import flash.events.TimerEvent;
var np:NativeProcess = new NativeProcess();
var npi:NativeProcessStartupInfo = new NativeProcessStartupInfo();
var args:Vector.<String> = new Vector.<String>();
var picsArr:Array = [];
var picNum :uint = 45;
for(var i:uint=1;i<=picNum;i++){
picsArr.push("Pic/"+i+".jpg");
}
//这里模拟了一个数组的图片,在软件同目录里有一个Pic目录
picsArr.push("Pic/45.jpeg");
picsArr.push("Pic/bg.png");
picsArr.push("Pic/logo.png");
picsArr.push("Pic/bg.png");
args.push("startImageProcessing");
args.push("Pic/temp/");
args.push("700");
args.push("300");
args.push("80");
args.push(picsArr);
npi.arguments = args;
npi.executable = File.applicationDirectory.resolvePath("MyNativeExe.exe");
np.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onMyData);
var firstRun:Boolean = true;
function onMyData(e:ProgressEvent):void {
while(np.standardOutput.bytesAvailable != 0) {
text.appendText(String.fromCharCode(np.standardOutput.readByte()));
}
text.appendText("\n");
var myData:Date = new Date();
var endTime:Number = myData.time;
text.appendText("结束时间:"+endTime+"\n");
text.appendText("消耗时间:"+(endTime-startTime)+"\n");
var curPicPath:String = picsArr[startID];
var tempArr:Array = curPicPath.split("/");
var curPicName :String = tempArr[tempArr.length-1];
var cArr = curPicName.split(".");
var midFileName:String= cArr[0] + "-mid." + cArr[1];
needLoadArr.push("Pic/temp/"+midFileName);
if(firstRun){
loadPic();
firstRun = false;
}
}
var startID:uint ;
var totalID:uint;
var loader:Loader;
var needLoadArr:Array ;
function loadPic(){
//检查needLoadArr数组里是否有数据需要加载
text1.appendText("needLoadArr数组里个数:"+needLoadArr.length);
if(needLoadArr.length>0){
text1.appendText("\n");
text1.appendText("当前ID1:"+startID+"\n");
var curPath:String = needLoadArr.shift();
}else{
checkTimer();
return;
}
if(!loader){
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,picLoaded);
}
loader.load(new URLRequest(curPath));
}
var myTimer:Timer;
function checkTimer(){
if(!myTimer){
myTimer = new Timer(100);
myTimer.addEventListener(TimerEvent.TIMER,onTimerFun);
}
myTimer.start();
}
function onTimerFun(e:TimerEvent):void{
if(needLoadArr.length>0){
loadPic();
myTimer.stop();
}
}
function picLoaded(e:Event):void{
startID++;
text1.appendText("图片加载完成-当前ID2:"+startID+"\n");
var bitd:BitmapData = loader.content as BitmapData;
loader.content.x = 20;
loader.content.y = 120;
addChild(loader.content);
if(startID<picsArr.length){
loadPic();
}else{
if(myTimer){
if(myTimer.running){
myTimer.stop();
myTimer.removeEventListener(TimerEvent.TIMER,onTimerFun);
myTimer = null;
}
}
}
}
startBtn.addEventListener(MouseEvent.CLICK,onBtnClick);
closeBtn.addEventListener(MouseEvent.CLICK,onBtnClick);
var startTime:Number;
function onBtnClick(e:MouseEvent):void{
var targetName:String = e.currentTarget.name;
if(targetName=="startBtn"){
var myData:Date = new Date();
startTime =myData.time;
text.appendText("开始时间:"+startTime+"\n");
np.start(npi);
startID = 0;
totalID = picsArr.length;
needLoadArr =[];
}else{
np.exit();
}
}
C#端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Drawing;
namespace WindowsFormsApplication1
{
static class Program
{
static private string saveUrl;
static private int maxWidth;
static private Double maxHeight;
static private Double quality;
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static int Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form1 myForm = new Form1();
int i = args.Length;
myForm.label1.Text = "hello world";
myForm.label1.Text += "\n";
myForm.label1.Text += "参数:"+i+"个数";
for (int t = 0; t < i; t++)
{
myForm.label1.Text += "\n";
myForm.label1.Text += args[t] is string+" :";
if (t==5)
{
string[] sArr = args[t].Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
myForm.label1.Text += "图片加载的个数:"+sArr.Length;
doImageProcessing(sArr);
}
else
{
myForm.label1.Text += args[t] + " :"+t;
switch (t)
{
case 1:
saveUrl = args[t];
break;
case 2:
maxWidth = Convert.ToInt32(args[t]);
break;
case 3:
maxHeight = Convert.ToInt32(args[t]);
break;
case 4:
quality = Convert.ToInt32(args[t]);
break;
}
}
}
Application.Run(myForm);
return 1;
}
static void doImageProcessing(string[] args)
{
int i = args.Length;
for (var t = 0; t < i; t++)
{
//CutForCustom(args[t]);
CutForCustom1(args[t]);
}
System.Console.Write("complete");
}
static void CutForCustom(string fileUrl)
{
System.IO.FileStream fs = new System.IO.FileStream(fileUrl, FileMode.Open, FileAccess.Read);
string[] sArr = fileUrl.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
string fileName = sArr[sArr.Length - 1];
string[] cArr = fileName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);
string midFileName = cArr[0] + "-mid." + cArr[1];
//Images.ZoomAuto(fs, saveUrl + midFileName, maxWidth, maxHeight, "", "");
}
static void CutForCustom1(string fileUrl)
{
Image image = Image.FromFile(fileUrl);
Image thumbnail = image.GetThumbnailImage(150,
100, null, IntPtr.Zero);
string[] sArr = fileUrl.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
string fileName = sArr[sArr.Length - 1];
string[] cArr = fileName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);
string midFileName = cArr[0] + "-mid." + cArr[1];
thumbnail.Save(saveUrl + midFileName);
}
}
}
测试项目的结构:
因为都很简单,所以没有过多的注释了,不明白的话,可以和我交流。
实测的话,从大图处理到小图生成,再到图片加载到AIR端,49张大图,230M的体积,大致用了12秒上下的样子,全部显示完毕。平均一张大图从处理到显示到客户端,用时12/50 近似于0.24s,完全符合客户使用的习惯。
如果这个话题就这么结束了,肯定不符合这次本文的要求,话说广州100年来第一次飘雪,能这么快就结束这篇技术讨论么?C++ 大哥都还没出场呢?
好了,给C++ 大哥一个机会。
二.C++大哥出来了
C++本人不熟,所以各种搜索资料,后来发现大部分人推荐C++类库:CXimage,Cimg,FreeImage等,我也是按这个顺序,依次研究了一遍。因为C++不熟悉,编译IDE也不熟悉,所以配置这个花费不少心思,各种报错一步步解决。最后还是。。。
先说CXimage ,这个大部分极力推荐,所以第一个那他开刀做研究,结果发现自己被他开刀了,也有可能不是他的问题,但问题就是在我电脑上跑不通。我还是大致讲述下这个:
(1) 下载这个类库,我下的是cximage600_full这个版本,ps 后来下cximage701_full也一样
(2)参考网上的教材,解压文件后,打开对应的项目文件,执行编译批生成,然后找到对应的h文件和lib文件,放到一个文件夹下,后面要用上
DLL这个目录事后发现可放可不放,大家可以先忽略
(3) 就是配置VS软件里,项目的库文件指引,就要把上述目录的地址,添加进去,一个是头文件指引,header ,一个是静态库 lib。
(4)然后可以运行提供的实例,或者自己写一些简单的测试。
可惜我一直没有能正常跑通,碰到过字符提示编码问题,碰到过提示电脑缺少某某dll,等等问题,我都一一参考网络资源搞定,最后还是死在程序运行后,提示一个0x000007b上,百度和google说是direct问题,用过修复工具DirectX_Repair-v3.3 不行,删除重装也不行。最后无奈放弃。。也许大家可以行的通,那就可以少走很多弯路了。
Cimg 这个类库呢,貌似跑的通,但是对文件格式支持,需要依赖一个cover.exe文件,测试效果不是太理想,感觉不是我需要的那种原生C++处理,所以放弃
FreeImage这个和CXimage 有点类似,也是提示错误,程序无法启动,所以心哇凉哇凉的。
这里各种恨广州的雪。。。。。。
三.继续研究缩略图
心凉也不能放弃,之前用第三方类库挖的坑,就是哭也要把自己埋进去了。公司没有C++的人,哥有人啊,所以就打电话给朋友:“喂,小明么? 此处省略500字”。朋友研究它的,我也不能闲着,再换换思路。
大家用win系统都很熟悉了,是不是发现系统自带的文件缩略图预览功能很方便?我靠,我是不是把大家心里想说的话给说出来了?
是啊,聪明你的,肯定开始各种脑补了,哈哈,是的,我也觉得有希望,所以又开始研究了。
资料有很多,我提供官方的吧:
Microsoft Office Thumbnails in SharePoint
Shell (including Windows 7 Libraries)
原来系统有一个C++API
GetThumbnailImage 官方解释提供,可以直接获取文件的缩略图对象
win系统会给所有的文件配备一个对应的缩略图示,系统自带生成后保存在用户电脑的目录里:
红色涂抹部分,大家用自己的电脑用户名替换
这里有一个db,其实这部分就是用户在平时操作后,文件关联的预览缩略图数据库,为什么我可以这么肯定?因为我也好奇,在好奇心下,分析了这个db结构和文件。
下面这个就是我的db的结构图:
随便选取一列,会显示对应的文件缩略图。 这也是为什么在系统预览图片,这么快捷方便的原因。
好了,既然都验证了,我们就用这个系统的GDI API去实现上述功能了。
实测效果确实高效,200多张图,2个G的量,现在属于秒级的处理速度。稍后再补充更新。未完待续。。。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
完结篇
C++的语法不是很熟,自己也是网上找到资料。
我只贴出关键的几个语法贴图,这几个语法,我上面也做过重点介绍的:
图片读取的逻辑:
img 对象是定义好了的:
//这里是一个循环,主要是处理一组批量的图片,下面的主要是图片路径的转换,最后指向img这个对象
strPath.Format( TEXT("%s\\%s"), m_strImageDir, *iter );
filenamenew.Format( TEXT("%s\\%s"),m_strImageDirnew, *iter );
USES_CONVERSION;
Bitmap img( A2W(strPath) );
GetThumbnailImage 这个上面详细讲过了,大家可以回顾
还有一个保存的逻辑:
都是内置的API接口,所以没有好说明的了
最后用AIR扩展,测试得到的效果:
文件和上述测试的文件一样:256M的体积,48张大图,从开始处理到结束,一共耗时667毫秒,体积增加的话,耗时量增加的也不算明显,所以项目的需求中,2G的体量,200张大图的情况,压力也不大,也是秒级的业务。
这个项目研究到此结束了,总的来说,坎坷之路,从不同的领域到不同的领域,思路换了又换,还好顺利完成调研需要,大家有类似问题,欢迎探讨。