# 捕获图像

Camera元素的主要功能之一是可以用来拍照。 下面将在一个简单的定格动画应用程序中使用它。 通过构建应用程序,将学习如何显示取景器、在相机之间切换、拍摄照片以及跟踪拍摄的照片。

用户界面如下所示。 它由三个主要部分组成:在背景中,可以找到取景器;右侧,是一列按钮;底部,是拍摄的图像列表。 想法是拍摄一系列照片,然后单击Play Sequence(播放序列)按钮播放图像,创建一个简单的定格动画电影。

image

# 取景器

相机的取景器部分使用VideoOutput元素作为CaptureSession的视频输出通道。 CaptureSession依次使用Camera组件来配置设备,这将显示来自摄像机的实时视频流。

CaptureSession {
    id: captureSession
    videoOutput: output
    camera: Camera {} 
    imageCapture: ImageCapture {
        onImageSaved: function (id, path) {
            imagePaths.append({"path": path})
            listView.positionViewAtEnd()
        }
    }
}

VideoOutput {
    id: output
    anchors.fill: parent
}

提示

可以通过使用专用的Camera属性(例如exposureModewhiteBalanceModezoomFactor)对相机行为进行更多控制。

# 捕获的图像列表

照片列表是一个水平的ListView,它显示来自名为imagePathsListModel的图像。 在背景中,使用了一个半透明的黑色Rectangle

ListModel {
    id: imagePaths
}

ListView {
    id: listView

    anchors.left: parent.left
    anchors.right: parent.right
    anchors.bottom: parent.bottom
    anchors.bottomMargin: 10

    height: 100

    orientation: ListView.Horizontal
    spacing: 10

    model: imagePaths

    delegate: Image {
        required property string path
        height: 100
        source: path
        fillMode: Image.PreserveAspectFit
    }

    Rectangle {
        anchors.fill: parent
        anchors.topMargin: -10

        color: "black"
        opacity: 0.5
    }
}

对于图像的拍摄,CaptureSession元素包含一组用于各种任务的子元素。 为了捕捉静态图片,使用了CaptureSession.imageCapture元素。 当调用captureToFile方法时,会拍摄一张图片并将其保存在用户的本地图片目录中,这将会导致CaptureSession.imageCapture发出imageSaved信号。

Button {
    id: shotButton
    
    width: parent.buttonWidth
    height: parent.buttonHeight

    text: qsTr("Take Photo")
    onClicked: {
        captureSession.imageCapture.captureToFile()
    }
}

在这种情况下,不需要显示预览图像,只需将生成的图像添加到屏幕底部的ListView即可。 如下例所示,保存图像的路径作为path参数随信号提供。






 
 
 



CaptureSession {
    id: captureSession
    videoOutput: output
    camera: Camera {} 
    imageCapture: ImageCapture {
        onImageSaved: function (id, path) {
            imagePaths.append({"path": path})
            listView.positionViewAtEnd()
        }
    }
}

提示

要显示预览,请连接到imageCaptured信号并使用信号参数preview作为Image元素的source。信号参数idimageCapturedimageSaved一起发送,这个值是从capture方法返回的。 使用它,可以在整个周期中跟踪图像的捕获。 这样,可以先使用预览,然后将其替换为正确保存的图像。 然而,在示例中并不是这样做的。

# 切换相机

如果用户有多个摄像头,提供一种在这些摄像头之间切换的方法会很方便使用。 可以通过将MediaDevices元素与ListView结合使用来实现此目的。 在此例子中,将使用一个ComboBox组件:

 
 
 







 








MediaDevices {
    id: mediaDevices
}

ComboBox {
    id: cameraComboBox

    width: parent.buttonWidth
    height: parent.buttonHeight

    model: mediaDevices.videoInputs
    textRole: "description"

    displayText: captureSession.camera.cameraDevice.description

    onActivated: function (index) {
        captureSession.camera.cameraDevice = cameraComboBox.currentValue
    }
}

ComboBoxmodel属性设置为MediaDevicesvideoInputs属性,videoInputs属性包含可用视频输入的列表。 然后将控件的displayText设置为相机设备的描述(captureSession.camera.cameraDevice.description)。

最后,当用户切换视频输入时,cameraDevice会更新以反映该变化,即captureSession.camera.cameraDevice = cameraComboBox.currentValue

# 回放

应用程序的最后一部分是实际播放。 这使用Timer元素和一些JavaScript函数驱动。 _imageIndex变量用于跟踪当前显示的图像。 显示到最后一张图像时,播放停止。 在示例中,root.state用于在播放序列时隐藏部分用户界面。

property int _imageIndex: -1

function startPlayback() {
    root.state = "playing"
    root.setImageIndex(0)
    playTimer.start()
}

function setImageIndex(i) {
    root._imageIndex = i

    if (root._imageIndex >= 0 && root._imageIndex < imagePaths.count) {
        image.source = imagePaths.get(root._imageIndex).path
    } else {
        image.source = ""
    }
}

Timer {
    id: playTimer

    interval: 200
    repeat: false

    onTriggered: {
        if (root._imageIndex + 1 < imagePaths.count) {
            root.setImageIndex(root._imageIndex + 1)
            playTimer.start()
        } else {
            root.setImageIndex(-1)
            root.state = ""
        }
    }
}
最后更新: 1/20/2022, 12:00:02 AM