Create screenshot and encode it in base64

55 Views Asked by At

I need to take a screenshot of the screen and save it to a folder. It is necessary to encode the resulting screenshot in base64 and pack it into Poco::JSON:Object.
I wrote the following code. I get a screenshot, but when I save it in jpeg format I get a black rectangle.
I also can't encode it in base64, my code crashes with exception:

JSON Exception [in file "/home/tania/.conan/data/poco/1.12.4/_/_/build/104aa542f7a964a7dcca5ea37969ea31f3c0ce48/src/Foundation/src/ErrorHandler.cpp", line 38]

file Screenshop.hpp:

namespace tasks::screenshot {
    class Screenshot : public Poco::Runnable {
    public:
        explicit Screenshot(Poco::Logger &logger);

        /**
        A constructror. Construct a X11Screenshot object.
        @param image a XImage pointer - image to process
        @param new_width integer - change initial image width to new value, default is 0 - no resize
        @param new_height integer - change initial image height to new value, default is 0 - no resize
        @param scale_type string - type of interpolation for scaling, available "linear" and "bilinear", default is "linear"
        */
        Screenshot(Poco::Logger &logger, XImage * image, int new_width=0, int new_height=0, std::string scale_type="linear");

        /**
        Public method to save image to jpeg file
        @param path a constant character pointer - path where to create jpeg image
        @param quality integer - level of jpeg compression, lower the value higher the compression
        @return a boolean - true if succesfuly created image file, false otherwise
        */
        bool save_to_jpeg(const char * path, int quality);

    private:

        void run() override;

        /**
        A private method to process XImage pixels to rgb bytes as is
        @param image an XImage pointer - image to process to rgb char vector
        @return vector of unsigned characters vectors - representing rgb values line by line
        */
        std::vector<std::vector<unsigned char>> process_original(XImage * image);
        /**
        A private method to process XImage pixels to rgb bytes with
        scale procesed by a lineral function (f(x) =  ax + b)
        @param image an XImage pointer - image to process to rgb char vector
        @param new_width a integer - scale to this max width
        @param new_height a integer - scale to this max height
        @return vector of unsigned characters vectors - representing rgb values line by line
        */
        std::vector<std::vector<unsigned char>> process_scale_linear(XImage * image, int new_width=0, int new_height=0);
        /**
        A private method to process XImage pixels to rgb bytes with
        scale procesed by a bilineral function (f(x, y) = a0 + a1x + a2y + a3xy)
        @param image an XImage pointer - image to process to rgb char vector
        @param new_width a integer - scale to this max width
        @param new_height a integer - scale to this max height
        @return vector of unsigned characters vectors - representing rgb values line by line
        */
        std::vector<std::vector<unsigned char>> process_scale_bilinear(XImage * image, int new_width=0, int new_height=0);

        Poco::Logger &logger_;

        // json парсер
        Poco::JSON::Parser json_parser_{};

        /**
        A private integer variable width
        Stores image current max width in pixels
        */
        int width = 0;
        /**
        A private integer variable height
        Stores image max height in pixels
        */
        int height = 0;
        /**
        A private vector of unsigned characters vectors image_data
        Contains rgb values of an image
        */
        std::vector<std::vector<unsigned char>> image_data = std::vector<std::vector<unsigned char>>();
    };
}

file Screenshot.cpp

#include "Screenshot.hpp"

namespace tasks::screenshot {

    Screenshot::Screenshot(Poco::Logger &logger)
            : logger_(logger)
    {

    }

    Screenshot::Screenshot(Poco::Logger &logger, XImage * image, int new_width, int new_height, std::string scale_type)
               : logger_(logger)
    {
        this->width = image->width;
        this->height = image->height;
        if ((new_width == 0 && new_height == 0) ||(new_width == this->width && new_height == this->height))
            this->image_data = this->process_original(image);
        else if (scale_type == "linear")
            this->image_data = this->process_scale_linear(image, new_width, new_height);
        else if (scale_type == "bilinear")
            this->image_data = this->process_scale_bilinear(image, new_width, new_height);
        else
            throw std::invalid_argument("Invalid initialisation parameters.");
    };

    void Screenshot::run()
    {
        logger_.information("Run screenshot task");

        Display* display = XOpenDisplay(nullptr);
        Window root = DefaultRootWindow(display);
        
        int width, height;

        XWindowAttributes attributes = {0};
        XGetWindowAttributes(display, root, &attributes);

        width = attributes.width;
        height = attributes.height;

        XImage* img = XGetImage(display, root, 0, 0 , width, height, AllPlanes, ZPixmap);

        logger_.information("получили скриншот экрана");

        XDestroyImage(img);
        XCloseDisplay(display);

        // теперь сожмем изображение
        int quality = 99;
        this->width = width;
        this->height = height;
        this->image_data = this->process_original(img);

        logger_.information("успешно выполнили сжатие изображения");
        std::string path = fmt::format("/home/tania/CLionProjects/c++/HelloWorld/screenshots/{0}.jpeg","test");

        if (this->save_to_jpeg(path.c_str(), quality)) {
            std::cout << "Successfully saved to " << path << std::endl;
        }

        //подготовим отправку на бэк-сервер
        std::stringstream string_stream;

        if (!image_data.empty())
        {
            Poco::Base64Encoder encoder(string_stream);
            encoder.write(reinterpret_cast<const char*> (image_data.data()),
                          static_cast<int64_t>          (image_data.size()));
            encoder.close();
        }

        logger_.information("приступим к парсингу в json: {0}",string_stream.str());
        auto parsed_json = json_parser_.parse(string_stream.str());
        logger_.information("упакуем в объект");
        Poco::JSON::Object pobject = *parsed_json.extract<Poco::JSON::Object::Ptr>();
        logger_.information(string_stream.str());
    }

    bool Screenshot::save_to_jpeg(const char * path, int quality)
    {
        FILE *fp = NULL;
        struct jpeg_compress_struct cinfo;
        struct jpeg_error_mgr jerr;
        JSAMPARRAY row;

        fp = fopen(path, "wb");
        if (!fp) {
            std::cout << "Failed to create file " << path << std::endl;
            return false;
        }
        cinfo.err = jpeg_std_error(&jerr);
        jpeg_create_compress(&cinfo);
        jpeg_stdio_dest(&cinfo, fp);
        cinfo.image_width = this->width;
        cinfo.image_height = this->height;
        cinfo.input_components = 3;
        cinfo.in_color_space = JCS_RGB;
        jpeg_set_defaults(&cinfo);
        jpeg_set_quality (&cinfo, quality, TRUE);
        jpeg_start_compress(&cinfo, TRUE);
        for(std::vector<std::vector<unsigned char>>::size_type i = 0; i != this->image_data.size(); i++) {
            row = (JSAMPARRAY) &this->image_data[i];
            jpeg_write_scanlines(&cinfo, row, 1);
        }
        jpeg_finish_compress(&cinfo);
        jpeg_destroy_compress(&cinfo);
        if (fp != NULL) fclose(fp);

        return true;
    };

    std::vector<std::vector<unsigned char>> Screenshot::process_original(XImage * image) {
        std::vector<std::vector<unsigned char>> image_data;
        std::vector<unsigned char> image_data_row;
        unsigned long red_mask = image->red_mask;
        unsigned long green_mask = image->green_mask;
        unsigned long blue_mask = image->blue_mask;

        for (int y = 0; y < this->height; y++) {
            for (int x = 0; x < this->width; x++) {
                unsigned long pixel = XGetPixel(image, x, y);

                unsigned char blue = pixel & blue_mask;
                unsigned char green = (pixel & green_mask) >> 8;
                unsigned char red = (pixel & red_mask) >> 16;

                image_data_row.push_back(red);
                image_data_row.push_back(green);
                image_data_row.push_back(blue);
            }
            image_data.push_back(image_data_row);
            image_data_row.clear();
        }

        return image_data;
    };

    std::vector<std::vector<unsigned char>> Screenshot::process_scale_linear(XImage * image, int new_width, int new_height){
        std::vector<std::vector<unsigned char>> image_data;
        std::vector<unsigned char> image_data_row;
        unsigned long red_mask = image->red_mask;
        unsigned long green_mask = image->green_mask;
        unsigned long blue_mask = image->blue_mask;
        float x_ratio = ((float) (this->width))/new_width;
        float y_ratio = ((float) (this->height))/new_height;

        for (int new_y=0; new_y < new_height; new_y++) {
            for (int new_x=0; new_x < new_width; new_x++) {
                unsigned long pixel = XGetPixel(image, (int) new_x * x_ratio, (int) new_y * y_ratio);

                unsigned char blue = pixel & blue_mask;
                unsigned char green = (pixel & green_mask) >> 8;
                unsigned char red = (pixel & red_mask) >> 16;

                image_data_row.push_back(red);
                image_data_row.push_back(green);
                image_data_row.push_back(blue);
            }
            image_data.push_back(image_data_row);
            image_data_row.clear();
        }
        // update width and height after resize
        this->width = new_width;
        this->height = new_height;
        return image_data;
    };

    std::vector<std::vector<unsigned char>> Screenshot::process_scale_bilinear(XImage * image, int new_width, int new_height){
        std::vector<std::vector<unsigned char>> image_data;
        std::vector<unsigned char> image_data_row;
        float x_ratio = ((float) (this->width))/new_width;
        float y_ratio = ((float) (this->height))/new_height;
        unsigned long red_mask = image->red_mask;
        unsigned long green_mask = image->green_mask;
        unsigned long blue_mask = image->blue_mask;

        for (int new_y=0; new_y < new_height; new_y++) {
            for (int new_x=0; new_x < new_width; new_x++) {

                // x1, y1 is coordinates original pixel to take from original image
                // x2 is step to the right
                //y2 is step down
                int x_1 =  new_x * x_ratio;
                if (x_1 >= this->width) x_1 = this->width - 1; //becouse start pint is 0 and final is 1 less
                int y_1 =  new_y * y_ratio;
                if(y_1 >= this->height) y_1 = this->height - 1; //becouse start pint is 0 and final is 1 less
                int x_2 = x_1 + x_ratio;
                if (x_2 >= this->width) x_2 = this->width - 1;
                int y_2 = y_1 + y_ratio;
                if(y_2 >= this->height) y_2 = this->height - 1;
                float x_diff = (x_ratio * new_x) - x_1 ;
                float y_diff = (y_ratio * new_y) - y_1 ;

                // 4 point for bilineral function
                unsigned long q_1_1 = XGetPixel(image, x_1, y_1);
                unsigned long q_1_2 = XGetPixel(image, x_1, y_2);
                unsigned long q_2_1 = XGetPixel(image, x_2, y_1);
                unsigned long q_2_2 = XGetPixel(image, x_2, y_2);
                // blue element
                // Yb = Ab(1-w1)(1-h1) + Bb(w1)(1-h1) + Cb(h1)(1-w1) + Db(wh)
                float blue = (q_1_1 & blue_mask) * (1 - x_diff) * (1 - y_diff) + (q_1_2 & blue_mask) * (x_diff) * (1 - y_diff) +
                             (q_2_1 & blue_mask) * (y_diff) * (1 - x_diff) + (q_2_2 & blue_mask) * (x_diff * y_diff);

                // green element
                // Yg = Ag(1-w1)(1-h1) + Bg(w1)(1-h1) + Cg(h1)(1-w1) + Dg(wh)
                float green = ((q_1_1 & green_mask) >> 8) * (1-x_diff) * (1-y_diff) + ((q_1_2 & green_mask) >> 8) * (x_diff) * (1 - y_diff) +
                              ((q_2_1 & green_mask) >> 8) * (y_diff) * (1-x_diff) + ((q_2_2 & green_mask) >> 8) * (x_diff * y_diff);

                // red element
                // Yr = Ar(1-w1)(1-h1) + Br(w1)(1-h1) + Cr(h1)(1-w1) + Dr(wh)
                float red = ((q_1_1 & red_mask) >> 16) * (1 - x_diff) * (1 - y_diff) + ((q_1_2 & red_mask) >> 16) * (x_diff) * (1 - y_diff) +
                            ((q_2_1 & red_mask) >> 16) * (y_diff) * (1 - x_diff) + ((q_2_2 & red_mask) >> 16) * (x_diff * y_diff);

                image_data_row.push_back((int) red);
                image_data_row.push_back((int) green);
                image_data_row.push_back((int )blue);
            }
            image_data.push_back(image_data_row);
            image_data_row.clear();
        }
        // update width and height after resize
        this->width = new_width;
        this->height = new_height;
        return image_data;
    };
}

fix code need in run() method. Please help me please.

0

There are 0 best solutions below