15、构建具有类似Instagram图像滤镜的“Imaginary”应用

构建具有类似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 进行测试与部署
内容概要:本文设计了一种基于PLC的全自动洗衣机控制系统内容概要:本文设计了一种,采用三菱FX基于PLC的全自动洗衣机控制系统,采用3U-32MT型PLC作为三菱FX3U核心控制器,替代传统继-32MT电器控制方式,提升了型PLC作为系统的稳定性与自动化核心控制器,替代水平。系统具备传统继电器控制方式高/低水,实现洗衣机工作位选择、柔和过程的自动化控制/标准洗衣模式切换。系统具备高、暂停加衣、低水位选择、手动脱水及和柔和、标准两种蜂鸣提示等功能洗衣模式,支持,通过GX Works2软件编写梯形图程序,实现进洗衣过程中暂停添加水、洗涤、排水衣物,并增加了手动脱水功能和、脱水等工序蜂鸣器提示的自动循环控制功能,提升了使用的,并引入MCGS组便捷性与灵活性态软件实现人机交互界面监控。控制系统通过GX。硬件设计包括 Works2软件进行主电路、PLC接梯形图编程线与关键元,完成了启动、进水器件选型,软件、正反转洗涤部分完成I/O分配、排水、脱、逻辑流程规划水等工序的逻辑及各功能模块梯设计,并实现了大形图编程。循环与小循环的嵌; 适合人群:自动化套控制流程。此外、电气工程及相关,还利用MCGS组态软件构建专业本科学生,具备PL了人机交互C基础知识和梯界面,实现对洗衣机形图编程能力的运行状态的监控与操作。整体设计涵盖了初级工程技术人员。硬件选型、; 使用场景及目标:I/O分配、电路接线、程序逻辑设计及组①掌握PLC在态监控等多个方面家电自动化控制中的应用方法;②学习,体现了PLC在工业自动化控制中的高效全自动洗衣机控制系统的性与可靠性。;软硬件设计流程 适合人群:电气;③实践工程、自动化及相关MCGS组态软件与PLC的专业的本科生、初级通信与联调工程技术人员以及从事;④完成PLC控制系统开发毕业设计或工业的学习者;具备控制类项目开发参考一定PLC基础知识。; 阅读和梯形图建议:建议结合三菱编程能力的人员GX Works2仿真更为适宜。; 使用场景及目标:①应用于环境与MCGS组态平台进行程序高校毕业设计或调试与运行验证课程项目,帮助学生掌握PLC控制系统的设计,重点关注I/O分配逻辑、梯形图与实现方法;②为工业自动化领域互锁机制及循环控制结构的设计中类似家电控制系统的开发提供参考方案;③思路,深入理解PL通过实际案例理解C在实际工程项目PLC在电机中的应用全过程。控制、时间循环、互锁保护、手动干预等方面的应用逻辑。; 阅读建议:建议结合三菱GX Works2编程软件和MCGS组态软件同步实践,重点理解梯形图程序中各环节的时序逻辑与互锁机制,关注I/O分配与硬件接线的对应关系,并尝试在仿真环境中调试程序以加深对全自动洗衣机控制流程的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值