构建具有类似Instagram图像滤镜的“Imaginary”应用
在本文中,我们将详细介绍如何构建一个名为“Imaginary”的应用程序,该应用具有类似Instagram的图像滤镜功能。下面是具体的实现步骤。
1. 初始化Sencha Touch应用
首先,我们使用Sencha Touch Cmd创建一个项目。具体操作如下:
$ cd /Development/touch-2.4.1
$ sencha generate app Imaginary ~/Projects/phonegap-by-example/imaginary
初始的文件夹和文件结构如下:
.
├── app
├── app.js
├── app.json
├── bootstrap.js
├── bootstrap.json
├── build
├── build.xml
├── cordova
├── index.html
├── packages
├── resources
└── touch
接下来,安装几个Cordova插件:
$ cd cordova
$ cordova plugin add org.apache.cordova.statusbar
$ cordova plugin add org.apache.cordova.camera
$ cordova plugin add org.apache.cordova.file
这些插件分别负责状态栏显示、相机访问和文件系统访问。我们还需要在
cordova/config.xml
文件的widget部分关闭之前添加状态栏配置:
<preference name="StatusBarOverlaysWebView" value="false" />
<preference name="StatusBarBackgroundColor" value="#000000" />
<preference name="StatusBarStyle" value="lightcontent" />
在
app.json
配置部分,指定目标平台为“ios”,并在
app.js
中添加一个视图和控制器:
views: [ 'Main' ],
controllers: [ 'Main' ]
2. 定义主视图和控制器
Main.js
视图表示一个带有标题栏和底部标签的Sencha Touch面板组件:
Ext.define('Imaginary.view.Main', {
extend: 'Ext.tab.Panel',
xtype: 'main',
requires: [
'Ext.TitleBar',
'Ext.Button',
'Ext.Img'
],
config: {
tabBarPosition: 'bottom',
items: [
//...
]
}
});
定义两个不同的标签页:
-
第一个标签页
:包含“Take Photo”按钮,点击该按钮应能看到相机弹出屏幕。
{
title: 'New Photo',
iconCls: 'lens',
items: [
{
docked: 'top',
xtype: 'titlebar',
title: 'New Photo'
},
{
xtype: 'container',
width: '100%',
height: '100%',
layout: {
type: 'vbox',
pack: 'center',
align: 'center'
},
items: [
{
xtype: 'button',
id: 'takePhotoBtn',
text: 'Take Photo',
iconCls: 'photo',
iconAlign: 'top',
height: 70,
width: 120,
padding: 10,
margin: 5
}
]
}
]
}
同时,在
resources/sass/app.scss
中定义按钮的图标类:
@include icon('lens', 'L');
@include icon('photo', 'v');
@include icon('globe', 'G');
@include icon('check', '3');
@include icon('gallery', 'P');
- 第二个标签页 :定义一个空容器,用于显示用相机拍摄的图片。
{
title: 'My Photos',
iconCls: 'gallery',
items: [
{
docked: 'top',
xtype: 'titlebar',
title: 'My Photos'
},
{
xtype: 'container',
id: 'photos',
width: '100%',
height: '100%',
scrollable: {
direction: 'vertical',
directionLock: true
}
}
]
}
Main.js
控制器中添加对按钮和图片容器的引用:
Ext.define('Imaginary.controller.Main', {
extend: 'Ext.app.Controller',
config: {
refs: {
takePhotoBtn: '#takePhotoBtn',
photoContainer: '#photos'
},
control: {
//...
}
},
//...
});
运行以下命令查看初始应用:
$ sencha app build -run native
3. 拍摄照片
在
Main.js
控制器中,为“Take Photo”按钮添加点击处理程序:
control: {
takePhotoBtn: {
tap: 'getPhoto'
}
}
定义一个变量来存储原始图像的链接:
originalImageUri: null
当用户点击“Take Photo”按钮时,调用
getPhoto
函数:
getPhoto: function() {
var self = this;
self.getCameraPicture(function(imageURI) {
self.originalImageUri = imageURI;
self.showPhotoPopup(imageURI);
});
}
getCameraPicture
函数用于访问相机插件:
getCameraPicture: function(callback) {
if (Ext.browser.is.PhoneGap) {
var onSuccess = function(imageURI) {
if (callback) callback(imageURI);
}
var onFail = function(message) {
alert('Failed because: ' + message);
if (callback) callback();
}
navigator.camera.getPicture(onSuccess, onFail, {
quality: 50,
targetWidth: 1000,
targetHeight: 1000,
destinationType:
navigator.camera.DestinationType.FILE_URI,
mediaType: navigator.camera.MediaType.PICTURE,
sourceType: navigator.camera.PictureSourceType.CAMERA,
encodingType: navigator.camera.EncodingType.JPEG,
correctOrientation: true,
saveToPhotoAlbum: false
});
} else {
// Emulate captured image
if (callback) callback('resources/images/test.jpg');
}
}
该函数会检查应用是在浏览器中运行还是在设备上作为原生应用运行。如果是浏览器,则模拟捕获的图像并返回本地测试图像的URL;如果是在设备上运行,则调用
navigator.camera.getPicture
函数。
4. 渲染效果列表
在显示拍摄的图片之前,需要先包含Pixastic库。
-
包含Pixastic库
:
下载Pixastic文件并将其放在
resources/lib
文件夹中,然后在
app.json
文件的
js
部分包含这些文件:
"js": [
{
"path": "resources/lib/pixastic.js"
},
{
"path": "resources/lib/pixastic.effects.js"
},
{
"path": "resources/lib/pixastic.worker.js"
},
{
"path": "resources/lib/pixastic.worker.control.js"
}
//...
]
同时,在
resources
部分指定
resources/lib
文件夹:
"resources": [
//...
"resources/lib"
]
-
显示图片弹出窗口
:
在getPhoto函数中调用self.showPhotoPopup(imageURI)函数,该函数创建一个新的面板并显示在主视图之上。
showPhotoPopup: function(imageURI) {
var self = this;
var popup = Ext.create('Imaginary.view.NewPicture');
Ext.Viewport.add(popup);
popup.show();
//...
self.setPreviewImage(imageURI);
popup.on('hide', function() {
popup.destroy();
});
Ext.getCmp('retakePhotoBtn').on('tap', function() {
self.getCameraPicture(self.setPreviewImage);
});
Ext.getCmp('savePhotoBtn').on('tap', function() {
//...
});
Ext.getCmp('cancelPhotoBtn').on('tap', function() {
popup.hide();
});
}
Imaginary.view.NewPicture
视图的定义如下:
Ext.define('Imaginary.view.NewPicture', {
extend: 'Ext.Panel',
xtype: 'newpicture',
requires: [
'Ext.TitleBar',
'Ext.Button',
'Ext.Img'
],
config: {
height: '100%',
width: '100%',
centered: true,
showAnimation: 'slideIn',
hideAnimation: 'slideOut',
hidden: true,
items: [
//...
]
}
});
该视图是一个全屏面板,具有滑入和滑出效果,默认隐藏。其包含标题栏和全屏容器:
{
docked: 'top',
xtype: 'titlebar',
title: 'New Photo'
},
{
xtype: 'container',
width: '100%',
height: '100%',
layout: {
type: 'vbox',
pack: 'center',
align: 'center'
},
items: [
//...
]
}
在容器中添加主要组件:图像预览、效果列表容器和操作按钮:
{
xtype: 'image',
id: 'photoPreview',
width: '100%',
flex: 8
},
{
xtype: 'container',
id: 'effectsContainer',
flex: 1,
width: '100%',
layout: {
type: 'hbox',
pack: 'center',
align: 'center'
},
scrollable: {
direction: 'horizontal',
directionLock: true
}
},
{
xtype: 'container',
flex: 1,
layout: {
type: 'hbox',
pack: 'center',
align: 'center'
},
items: [
{
xtype: 'button',
id: 'retakePhotoBtn',
text: 'Retake',
flext: 1,
margin: '0 5 0 5'
},
{
xtype: 'button',
id: 'savePhotoBtn',
text: 'Save',
flext: 1,
margin: '0 5 0 5'
},
{
xtype: 'button',
id: 'cancelPhotoBtn',
text: 'Close',
flext: 1,
margin: '0 5 0 5'
}
]
}
setPreviewImage
函数用于设置图片的源:
setPreviewImage: function(imageURI) {
Ext.getCmp('photoPreview').setSrc(imageURI);
}
定义几个事件处理程序来处理弹出窗口的隐藏、重新拍摄、保存和取消按钮的点击事件。
5. 定义效果模型和存储
为了正确处理效果列表,定义效果模型和存储:
-
效果模型
:
Ext.define('Imaginary.model.Effect', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'name', type: 'string' },
{ name: 'options', type: 'auto' }
]
}
});
该模型包含两个字段:
name
和
options
。
-
效果存储
:
Ext.define('Imaginary.store.Effects', {
extend: 'Ext.data.Store',
requires: ['Imaginary.model.Effect'],
config: {
storeId: 'Effects',
model: 'Imaginary.model.Effect',
data: [
{ name: 'posterize', options: { levels: 5 } },
{ name: 'solarize' },
//..
]
}
});
总共有29种效果。
6. 应用效果到缩略图
- 调整图像大小 :
resizeImage: function(imageURI, callback) {
var img = new Image();
img.onload = function () {
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
var ratio = 160/img.width;
var newWidth = img.width*ratio;
var newHeight = img.height*ratio;
oc.width = newWidth;
oc.height = newHeight;
octx.drawImage(img, 0, 0, newWidth, newHeight);
if (callback) callback(oc.toDataURL(), oc, octx);
};
img.src = imageURI;
}
该函数使用HTML5 Canvas元素将图像大小调整为宽度不超过160像素,并返回图像的Base64表示。
-
应用Pixastic效果
:
applyEffect: function(effect, thumbData, callback) {
var img = new Image();
img.onload = function () {
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
oc.width = img.width;
oc.height = img.height;
octx.drawImage(img, 0, 0, img.width, img.height);
P = new Pixastic(octx, 'resources/lib/');
P[effect.name](effect.options).done(function() {
var data = oc.toDataURL();
if (callback) callback(data);
}, function(p) {
// display progress here;
});
};
img.src = thumbData;
}
- 获取效果列表 :
getEffects: function() {
var effectsStore = Ext.getStore('Effects');
var data = effectsStore.getData();
return data ? data.all : null;
}
-
创建效果缩略图列表
:
在showPhotoPopup函数中添加以下代码:
showPhotoPopup: function(imageURI) {
//...
var effects = self.getEffects();
var effectsContainer = Ext.getCmp('effectsContainer');
self.resizeImage(imageURI, function(thumbData) {
effects.forEach(function(item) {
var effect = item.getData();
var effectImage = Ext.create('Ext.Img', {
src: thumbData,
height: 64,
width: 64,
margin: '2 2 2 2'
});
effectImage.on('tap', function() {
//...
});
effectsContainer.add(effectImage);
});
var thumbs = effectsContainer.getItems();
var i = 0;
var item = thumbs.items[i];
self.applyEffect(effects[i].data, thumbData, function
cb(thumbDataFiltered) {
if (i < thumbs.items.length-1) {
item.setSrc(thumbDataFiltered);
i++;
item = thumbs.items[i];
return self.applyEffect(effects[i].data, thumbData, cb);
}
});
});
//...
}
通过以上步骤,我们成功地将效果应用到缩略图上。
7. 应用效果到照片
为了在处理大图片时提供更好的用户体验,可以实现一个预加载器。在
app/LoadMask.js
文件中添加以下代码:
Ext.define('Imaginary.LoadMask', {
extend: 'Ext.LoadMask',
xtype: 'loadmask',
config: {
message: '',
html: '<div class="loader">'+
'<div class="item-1"><span></span></div>'+
'<div class="item-2"><span></span></div>'+
'<div class="item-3"><span></span></div>'+
'<div class="item-4"><span></span></div>'+
'<div class="item-5"><span></span></div>'+
'<div class="item-6"><span></span></div>'+
'</div>',
zIndex: 3000
}
});
在缩略图的点击事件处理程序中添加以下代码:
effectImage.on('tap', function() {
var thumbs = effectsContainer.getItems();
var index = thumbs.items.indexOf(this);
popup.setMasked({ xclass: 'Imaginary.LoadMask' });
self.applyEffect(effects[index].data, self.originalImageUri,
function(imageDataFiltered) {
self.setPreviewImage(imageDataFiltered);
popup.setMasked(false);
});
});
当点击缩略图时,会显示预加载器,处理完图片后替换大图片的源并隐藏预加载器。
总结
通过以上步骤,我们成功构建了一个具有类似Instagram图像滤镜功能的应用程序。从初始化项目、拍摄照片、渲染效果列表到应用效果到照片,每一步都有详细的代码实现。接下来,我们将实现保存图片的功能。
流程图
graph TD;
A[初始化Sencha Touch应用] --> B[定义主视图和控制器];
B --> C[拍摄照片];
C --> D[渲染效果列表];
D --> E[定义效果模型和存储];
E --> F[应用效果到缩略图];
F --> G[应用效果到照片];
G --> H[保存图片];
表格
| 步骤 | 操作 |
|---|---|
| 1 | 使用Sencha Touch Cmd创建项目 |
| 2 | 安装Cordova插件 |
| 3 | 定义主视图和控制器 |
| 4 | 为“Take Photo”按钮添加点击处理程序 |
| 5 | 包含Pixastic库 |
| 6 | 显示图片弹出窗口 |
| 7 | 定义效果模型和存储 |
| 8 | 调整图像大小并应用效果到缩略图 |
| 9 | 应用效果到照片并实现预加载器 |
| 10 | 保存图片 |
8. 保存处理后的照片
接下来,我们要实现保存处理后照片的功能。在之前定义的
savePhotoBtn
的点击事件处理程序中添加保存逻辑。
Ext.getCmp('savePhotoBtn').on('tap', function() {
var self = this;
if (Ext.browser.is.PhoneGap) {
// 将 Base64 数据转换为文件
var dataUrl = Ext.getCmp('photoPreview').getSrc();
var parts = dataUrl.split(';base64,');
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
var blob = new Blob([uInt8Array], { type: contentType });
// 获取文件系统
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) {
fs.root.getDirectory("Pictures", { create: true, exclusive: false }, function(dirEntry) {
var fileName = new Date().getTime() + ".jpg";
dirEntry.getFile(fileName, { create: true, exclusive: false }, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.write(blob);
alert('照片保存成功!');
}, function(error) {
alert('保存照片时出错: ' + error.code);
});
}, function(error) {
alert('创建文件时出错: ' + error.code);
});
}, function(error) {
alert('获取目录时出错: ' + error.code);
});
}, function(error) {
alert('获取文件系统时出错: ' + error.code);
});
} else {
// 在浏览器中模拟保存
var link = document.createElement('a');
link.href = Ext.getCmp('photoPreview').getSrc();
link.download = 'filtered_photo.jpg';
link.click();
}
});
上述代码实现了在不同环境下保存照片的功能:
- 在原生设备上,将 Base64 数据转换为
Blob
对象,然后使用 Cordova 的文件系统 API 将其保存到设备的
Pictures
目录下。
- 在浏览器中,创建一个下载链接并触发点击事件来模拟保存操作。
9. 优化用户体验
为了提升用户体验,我们可以添加一些额外的功能,例如:
-
提示信息
:在拍摄照片、应用效果和保存照片时,提供更详细的提示信息,让用户清楚知道操作的进度和结果。
-
进度条
:在应用效果和保存照片时,显示进度条,让用户了解操作的进度。
-
错误处理
:对可能出现的错误进行更详细的处理,例如相机访问失败、文件保存失败等,给用户提供明确的错误信息。
提示信息优化
在之前的代码中,我们已经使用了
alert
来显示一些简单的提示信息。可以进一步优化,使用自定义的提示框组件。
// 定义一个简单的提示框组件
Ext.define('Imaginary.view.MessageBox', {
extend: 'Ext.Panel',
xtype: 'messagebox',
config: {
centered: true,
modal: true,
hideOnMaskTap: true,
width: 200,
height: 100,
layout: {
type: 'vbox',
pack: 'center',
align: 'center'
},
items: [
{
xtype: 'label',
itemId: 'messageLabel'
},
{
xtype: 'button',
text: '确定',
handler: function() {
this.up('messagebox').hide();
}
}
]
},
showMessage: function(message) {
this.down('#messageLabel').setHtml(message);
this.show();
}
});
// 在需要显示提示信息的地方使用
var messageBox = Ext.create('Imaginary.view.MessageBox');
// 在相机访问失败时显示提示
var onFail = function(message) {
messageBox.showMessage('相机访问失败: ' + message);
if (callback) callback();
}
// 在文件保存成功时显示提示
fileWriter.write(blob);
messageBox.showMessage('照片保存成功!');
进度条优化
在应用效果和保存照片时,显示进度条。可以使用 HTML5 的
progress
元素来实现。
// 在应用效果时显示进度条
P = new Pixastic(octx, 'resources/lib/');
P[effect.name](effect.options).progress(function(p) {
var progressBar = Ext.getCmp('progressBar');
if (progressBar) {
progressBar.setValue(p * 100);
}
}).done(function() {
var data = oc.toDataURL();
if (callback) callback(data);
var progressBar = Ext.getCmp('progressBar');
if (progressBar) {
progressBar.hide();
}
}, function(p) {
// display progress here;
});
// 在保存照片时显示进度条
fileWriter.onprogress = function(e) {
if (e.lengthComputable) {
var progressBar = Ext.getCmp('progressBar');
if (progressBar) {
var percentComplete = (e.loaded / e.total) * 100;
progressBar.setValue(percentComplete);
}
}
};
fileWriter.onwriteend = function() {
var progressBar = Ext.getCmp('progressBar');
if (progressBar) {
progressBar.hide();
}
messageBox.showMessage('照片保存成功!');
};
10. 测试与部署
在完成所有功能的开发后,需要进行全面的测试,确保应用在不同设备和环境下都能正常运行。
测试
- 功能测试 :测试拍摄照片、应用效果、保存照片等核心功能是否正常工作。
- 兼容性测试 :在不同的移动设备(如 iPhone、Android 手机)和浏览器上进行测试,确保应用的兼容性。
- 性能测试 :测试应用的响应速度和资源占用情况,优化性能瓶颈。
部署
- 打包应用 :使用 Sencha Touch Cmd 或 Cordova 工具将应用打包成原生应用(如 iOS 和 Android 应用)。
- 发布应用 :将打包好的应用发布到应用商店(如 App Store 和 Google Play)供用户下载和使用。
总结
通过以上步骤,我们成功构建了一个具有类似 Instagram 图像滤镜功能的应用程序,并实现了保存照片和优化用户体验的功能。整个开发过程包括初始化项目、拍摄照片、渲染效果列表、应用效果到照片、保存照片以及优化用户体验等环节。每个环节都有详细的代码实现和操作步骤。
流程图
graph TD;
A[初始化项目] --> B[拍摄照片];
B --> C[渲染效果列表];
C --> D[应用效果到照片];
D --> E[保存照片];
E --> F[优化用户体验];
F --> G[测试与部署];
表格
| 步骤 | 操作 |
|---|---|
| 1 | 初始化 Sencha Touch 应用 |
| 2 | 安装 Cordova 插件 |
| 3 | 定义主视图和控制器 |
| 4 | 实现拍摄照片功能 |
| 5 | 包含 Pixastic 库并渲染效果列表 |
| 6 | 定义效果模型和存储 |
| 7 | 应用效果到缩略图和照片 |
| 8 | 实现保存照片功能 |
| 9 | 优化用户体验(提示信息、进度条、错误处理) |
| 10 | 进行测试与部署 |
超级会员免费看
37

被折叠的 条评论
为什么被折叠?



