很多网站在上传用户头象时,除了传统方式上传外,都支持在线摄像头拍照并做简单编辑,完成之后再将图象数据提交到服务端(比如ASP.Net),这几天正好需要这个功能,研究了下,思路如下:
1、先获取摄像头视频
2、利用BitmapData.draw来对视频截图
3、在截图上,放一个方块允许用户手动调整位置,同时允许截图做缩放
4、用户调整完成后,对指定区域的BitmapData做copyPixes处理(即拷贝指定区域的像素)
5、将上一步得到的新BitmapData进行Jpeg编码压缩,然后再转到base64字符串
6、将字符串发送到ASP.Net,然后还原成图片保存即可(即base64的解码)
flash端代码:(第6步省略了)
import flash.media.Camera;
import flash.text.TextField;
import flash.media.Video;
import flash.text.TextFormat;
import fl.controls.Button;
import flash.utils.Timer;
import flash.events.MouseEvent;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.geom.Rectangle;
import flash.events.Event;
var _cam:Camera;
var _txtInfo:TextField = txtInfo;
var _txtMsg:TextField = txtMsg;
var _btnGet:Button = btnGet;
var _btnSave:Button = btnSave;
var _video:Video = myVideo;
var _timer:Timer;
var _videoIsWorked:Boolean = false;
var _bmp:Bitmap;
var _bmpTest:Bitmap;
var _mask:Sprite;
var _slider:SimpleSlider;
init();
function init():void
{
this._txtMsg.visible = false;
this._btnGet.label = "拍 照";
this._btnSave.label = "保 存";
this._txtInfo.selectable = false;
var style:TextFormat = new TextFormat();
style.font = "宋体";
style.size = 12;//字体大小
style.color = 0x000000;//RGB色
style.leading = 7;//行间距
this._btnGet.setStyle("textFormat",style);
this._btnSave.setStyle("textFormat",style);
getCamera();
}
function getCamera():void
{
this._btnGet.enabled = false;
this._btnSave.enabled = false;
this._cam = Camera.getCamera();
if (_cam == null)
{
this._txtInfo.text = "未检测到摄像头设备!";
return;
}
_cam.addEventListener(StatusEvent.STATUS, cameraStatusHandler);
_cam.addEventListener(ActivityEvent.ACTIVITY, cameraActivityHandler);
_cam.setQuality(0,0);
this._video.attachCamera(this._cam);
}
//用户选择是否摄像头时触发 ;
function cameraStatusHandler(e:StatusEvent):void
{
if (e.code == "Camera.Muted")
{
this._txtInfo.text = "您不允许使用摄像头!";
}
else if (e.code == "Camera.Unmuted")
{
this._txtInfo.text = "摄像头视频获取中...";
_timer = new Timer(100,20);//每隔100ms检测摄像头状态,一共检测20次
cameraActivityHandler(null);
}
}
//摄像头有活动时被触发
function cameraActivityHandler(e:ActivityEvent):void
{
if (! _videoIsWorked)
{
if (_timer != null)
{
_timer.addEventListener(TimerEvent.TIMER, checkCamera);
_timer.addEventListener(TimerEvent.TIMER_COMPLETE, checkCameraComplete);
_timer.start();
}
}
}
//timer回调函数,用于检测摄像头设备是否正确
function checkCamera(e:TimerEvent):void
{
this._txtInfo.text = "摄像头视频获取中...";
if (this._cam.currentFPS > 0)
{
_timer.stop();
_videoIsWorked = true;
this._txtInfo.text = "";//摄像头工作正常
this._txtInfo.text = "摄像头正常,请点击拍照";
//trace("_cam.width=",_cam.width,",_cam.height=",_cam.height);
this._btnGet.addEventListener(MouseEvent.CLICK,btnGet_Click);
this._btnGet.enabled = true;
this._video.width = 240;
this._video.height = 180;
//trace(this._video.width,this._video.height);
}
}
function btnGet_Click(e:MouseEvent):void
{
var bmd:BitmapData = new BitmapData(_cam.width,_cam.height);
bmd.draw(_video);
//trace(bmd.width,bmd.height);
if (this._bmp == null)
{
this._bmp = new Bitmap(bmd);
addChild(_bmp);
_bmp.x = 260;
_bmp.y = 10;
_bmp.width = _video.width;
_bmp.height = _video.height;
if (this._mask == null)
{
this._mask = new Sprite();
this._mask.graphics.beginFill(0xffffff,0.3);
this._mask.graphics.drawRect(0,0,125,125);
this._mask.graphics.endFill();
this._mask.graphics.lineStyle(1,0xff0000,1);
this._mask.graphics.lineTo(1,124);
this._mask.graphics.lineTo(124,124);
this._mask.graphics.lineTo(124,1);
this._mask.graphics.lineTo(1,1);
addChild(this._mask);
this._mask.x = 260 + 57;
this._mask.y = 10 + 27;
this._mask.buttonMode = true;
this._mask.addEventListener(MouseEvent.MOUSE_DOWN,mask_startDrag);
this.stage.addEventListener(MouseEvent.MOUSE_UP,stage_endDrag);
if (_slider == null)
{
_slider = new SimpleSlider(125,180,180);
_slider.backWidth = 120;
_slider.backHeight = 6;
_slider.handleHeight = 16;
_slider.addEventListener(Event.CHANGE,slider_Change);
addChild(_slider);
_slider.x = 320;
_slider.y = 205;
this._txtMsg.visible = true;
}
}
}
else
{
this._bmp.bitmapData = bmd;
}
this._btnSave.enabled = true;
this._txtInfo.text = "拖动左侧方块可调整位置"
;
this._btnSave.addEventListener(MouseEvent.CLICK,btnSave_Click);
}
function btnSave_Click(e:Event):void
{
//因为视频的大小跟摄像头原始像素的尺寸并不一致(即视频可能缩放过了,所以要用比例调整回最初始的状态)
var ratio:Number = this._cam.width / this._bmp.width;
var imgWidth:int = this._mask.width * ratio;
var imgHeight:int = this._mask.height * ratio;
//trace(ratio);
var bmd:BitmapData = new BitmapData(imgWidth,imgHeight,false,0xffff99);
bmd.copyPixels(this._bmp.bitmapData,new Rectangle((this._mask.x-260)*ratio,(this._mask.y - 10)*ratio,imgWidth,imgHeight),new Point());
if (this._bmpTest == null)
{
this._bmpTest = new Bitmap(bmd);
addChild(_bmpTest);
_bmpTest.x = 375;
_bmpTest.y = 230;
}
else
{
this._bmpTest.bitmapData = bmd;
}
_bmpTest.width = this._mask.width;
_bmpTest.height = this._mask.height;
trace("_bmpTest.width=",this._mask.width,",_bmpTest.height=",this._mask.height);
var jpegEncoder:JPGEncoder = new JPGEncoder();
var jpegArr:ByteArray = jpegEncoder.encode(bmd);//压缩成jpeg
trace(jpegArr.length);
var base64string:String = Base64.encode(jpegArr);
txtBase64.text = base64string;
trace(base64string.length);
}
function slider_Change(e:Event):void
{
//trace(e);
this._bmp.height = this._slider.value;
this._bmp.width = this._bmp.height * 4 / 3;
}
function mask_startDrag(e:MouseEvent):void
{
this._mask.startDrag(false,new Rectangle(260,10,240-125,180-125));
}
function stage_endDrag(e:MouseEvent):void
{
this._mask.stopDrag();
//trace("x=",_mask.x);
//trace("y=",_mask.y);
}
function checkCameraComplete(e:TimerEvent):void
{
this._txtInfo.text = "设备无法使用!(有可能被占用)";
_timer.removeEventListener(TimerEvent.TIMER, checkCamera);
_timer.removeEventListener(TimerEvent.TIMER_COMPLETE, checkCameraComplete);
_timer = null;
}
Flash在线演示(需要电脑上连接好摄像头)
C#端的处理示例:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Web;
namespace OnlinePhotoCapture
{
/// <summary>
/// 将图片base64字符串原还原为图片并调整尺寸后保存(by 菩提树下的杨过http://yjmyzz.cnblogs.com/)
/// </summary>
public class SavePhoto : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string base64String = "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";
Image bmp = null;
MemoryStream ms = null;
try
{
byte[] b = Convert.FromBase64String(base64String);
ms = new MemoryStream(b);
bmp = ImageResize(new Bitmap(ms),125,125);
bmp.Save(context.Request.MapPath("logo.jpg"), ImageFormat.Jpeg);
context.Response.Write("success");
}
catch (Exception ex)
{
context.Response.Write("failure:" + ex.Message.ToString());
}
finally
{
bmp.Dispose();
ms.Dispose();
}
}
/// <summary>
/// 修改图片尺寸
/// </summary>
/// <param name="imgSrc"></param>
/// <returns></returns>
private Image ImageResize(Image imgSrc,int width,int height)
{
Image newImage = imgSrc.GetThumbnailImage(125, 125, null, new IntPtr());
Graphics g = Graphics.FromImage(newImage);
g.DrawImage(newImage, 0, 0, newImage.Width, newImage.Height);
g.Dispose();
return newImage;
}
public bool IsReusable
{
get
{
return false;
}
}
}
}