GTK是一款开源的、面向多平台的GUI工具箱,其英文全称为GIMP Toolkit。最初是Peter Mattis 和 Spencer Kimball 为GNU Image Manipulation Program (GIMP)编写。在后续的发展中,它已经成为通用的GUI库,应用于越来越多的程序,Linux平台的图形应用程序的半壁江山都是使用GTK编写的。

使用

目前GTK已经基本稳定,然而由于越来越多的其他GUI库出现,以及HTML5的统治,目前GTK系列的需求已经较少。

在比较早之前,就一直在关注GTK,然后当年水平有限,以及精力以及方向并不在这方面,所以也就并没有怎么了解以及使用,最近有个项目需要用到界面,然后为了方便调试(主要是开发电脑与实际电脑不在同一设备上,也不是同一架构,不想要来回跑动,所以使用了SSH X11 转发,而GTK可以很好地支持)。

废话不多说,上代码看看:

//
// Created by caesar kekxv on 2020/3/18.
//
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
#include <gtk/gtk.h>
#include <thread>
#include <atomic>

#if defined(LINUX) || defined(linux)
#include <X11/Xlib.h>
#endif
#ifdef WIN32
#include "getopt.h"
#else
#include <getopt.h>
#endif

using namespace std;
using namespace cv;

int X_WIDTH       =   640;
int X_HEIGHT      =   480;

VideoCapture cap;
cv::Mat last_frame;
int fps = 30;

// 是否关闭
std::atomic<bool> stoped{false};
GMutex _mutex;
std::thread video_task;

GtkWidget *image = nullptr;
/* Surface to store current scribbles */
static cairo_surface_t *surface = nullptr;
/**
 * OpenCV Mat 格式转换为 GTK GdkPixbuf 格式
 */
GdkPixbuf *MatToGdkPixbuf(Mat inMat) {
    // IplImage -> GdkPixbuf
    GdkPixbuf *src = gdk_pixbuf_new_from_data(
            (const guchar *) inMat.data, GDK_COLORSPACE_RGB, false, 8, inMat.size().width, inMat.size().height, inMat.step, nullptr, nullptr);
    return src;
}

/**
 * 清理画布
 */
static void clear_surface() {
    if (surface == nullptr)return;
    cairo_t *cr;
    cr = cairo_create(surface);
    cairo_set_source_rgb(cr, 1, 1, 1);
    cairo_paint(cr);
    cairo_destroy(cr);
}

/* Create a new surface of the appropriate size to store our scribbles */
static gboolean configure_event_cb(GtkWidget *widget,
                   GdkEventConfigure *event,
                   gpointer data) {
    if (surface)
        cairo_surface_destroy(surface);
    surface = gdk_window_create_similar_surface(gtk_widget_get_window(widget),
                                                CAIRO_CONTENT_COLOR,
                                                gtk_widget_get_allocated_width(widget),
                                                gtk_widget_get_allocated_height(widget));
    /* Initialize the surface to white */
    clear_surface();
    /* We've handled the configure event, no need for further processing. */
    return TRUE;
}

/**
 * 关闭窗口释放
 */
static void close_window() {
    // std::unique_lock<std::mutex> _lock{m_lock};
    if (surface)
        cairo_surface_destroy(surface);
    surface = nullptr;
}


/* Redraw the screen from the surface. Note that the ::draw
 * signal receives a ready-to-be-used cairo_t that is already
 * clipped to only draw the exposed areas of the widget
 */
static gboolean draw_event(GtkWidget *widget, cairo_t *cr) {
    GdkWindow *win;
    win = gtk_widget_get_window(widget);
    g_mutex_lock(&_mutex);
    if (!last_frame.empty()) {
        auto src = MatToGdkPixbuf(last_frame);
        g_mutex_unlock(&_mutex);
        if (src == nullptr) {
            return FALSE;
        }
        gdk_cairo_set_source_pixbuf(cr, src, 0, 0);
        cairo_paint(cr);
        cairo_fill(cr);
        g_object_unref(src);
    } else
        g_mutex_unlock(&_mutex);
    return TRUE;
}

/**
 * OpenCV 摄像头 线程
 */
void video_task_run(GtkWidget *_image) {
    while (!stoped.load() && cap.isOpened()) {
        Mat frame;
        cap >> frame;
        if (frame.empty())break;

        resize(frame, frame, Size(X_WIDTH, X_HEIGHT));
        cvtColor(frame, frame, COLOR_BGR2RGB);
        if (surface == nullptr) {
            usleep(1000 / fps * 1000);
            continue;
        }
        // GTK 锁
        g_mutex_lock(&_mutex);
        if (surface == nullptr) {
            g_mutex_unlock(&_mutex);
            break;
        }
        last_frame = frame;
        // 信号通知
        g_idle_add((GSourceFunc) gtk_widget_queue_draw, (void *) _image);
        // gdk_threads_add_idle((GSourceFunc) gtk_widget_queue_draw, (void *) _image);
        g_mutex_unlock(&_mutex);

        usleep(1000 / fps * 800);
        g_idle_remove_by_data((void *) _image);
    }
    // logger::instance()->d(__FILENAME__, __LINE__, "视频读取完毕");
}

static void activate(GtkApplication *app, gpointer user_data) {
    GtkWidget *window;
    window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW (window), "Window");
    gtk_window_set_default_size(GTK_WINDOW (window), X_WIDTH, X_HEIGHT);
    gtk_window_set_position( GTK_WINDOW(window), GTK_WIN_POS_CENTER_ALWAYS );

    g_signal_connect (window, "destroy", G_CALLBACK(close_window), NULL);

    image = gtk_drawing_area_new();

    gtk_widget_set_size_request(image, X_WIDTH, X_HEIGHT);
    gtk_container_add(GTK_CONTAINER (window), image);

    /* Signals used to handle the backing surface */
    g_signal_connect (image, "draw",
                      G_CALLBACK(draw_event), NULL);

    g_signal_connect (image, "configure-event",
                      G_CALLBACK(configure_event_cb), NULL);

    video_task = thread(video_task_run, image);
    video_task.detach();

    gtk_widget_show_all(window);
}


int main(int argc, char *argv[]) {
#if defined(LINUX) || defined(linux)
    setenv("DISPLAY", "localhost:10.0", 1);
    // setenv("DISPLAY", ":1", 1);
    logger::instance()->i(__FILENAME__, __LINE__, "XOpenDisplay : %s", XOpenDisplay(nullptr) ? "True" : "False");
#endif

    cap.open(0);
    if (cap.isOpened()) {
        fps = cap.get(CAP_PROP_FPS);
        X_WIDTH = cap.get(CAP_PROP_FRAME_WIDTH);
        X_HEIGHT = cap.get(CAP_PROP_FRAME_HEIGHT);
        cap.read(last_frame);
    }
    GtkApplication *app;
    int status;

    app = gtk_application_new("com.example", G_APPLICATION_FLAGS_NONE);
    g_signal_connect (app, "activate", G_CALLBACK(activate), NULL);
    status = g_application_run(G_APPLICATION (app), 1, argv);
    g_object_unref(app);

    return status;
}

如果有什么错误或者不明白的地方,可以留言告诉我。