Magic image——Alpha Channel

半夜翻到某条说说之后,看到星空就突然想到怎么写了~ 于是果断起来嗨!最后也总算是写出来了(然而写完的时候re:0也没更新……)

于是可以先看看下面的两张图

hoshizora

hoshizora

看起来好像是两张不同的图,但是实际上是同一张哦~(=゚ω゚)ノ

xcode

整体的想法比较简单,我们姑且把上面的第一张称为front layer,那么对应的,看起来是星空的那张就称为back layer.

这样的效果主要是利用了在展示图片时,(到目前为止)通常在缩略图/预览图的界面会使用白色作为底色,当进入沉浸式(或者简单来说就是欣赏图片)的时候,会使用黑色作为底色。

知道了这一点之后,我们可以推断front layer中看到的白色,实际上(多数)是由底色提供的,back layer中的黑色也是如此。

那么接下来要做的就是

  • 以灰度模式读入front layer和back layer
  • 将front layer中的“白色”换为透明色
  • 将back layer中的“黑色”换为透明色
  • 合并两张图,保存为支持Alpha通道的图像格式

跳到实际代码~

当然,上面提到的“白色”、“黑色”都是相对而言,我们将会使用一个阀值(threshold)来控制。front layer中,小于threshold的像素点将会使用透明色。back layer中,大于threshold的像素点将会使用透明色。这个值可以根据实际情况调整,比如front layer色调明亮,back layer以暗色调为主时,则可控制在128~192左右。若是两者均以亮色为主,则需要调高一些。

下面两张图分别作为front layer和back layer,threshold为224。

frontlayer
frontlayer
backlayer
backlayer

做出的效果如下

Rem
Rem
Rem
Rem
实际用到代码如下,也可以在Github上找到hoshizora
#include <getopt.h>
#include <stdlib.h>
#include <cmath>
#include <opencv2/opencv.hpp>
#include <opencv2/core/version.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;

#if (CV_VERSION_MAJOR < 3)
#define CV_CVTCOLOR_BGR2GREY  CV_BGR2GRAY
#define CV_CVTCOLOR_GREY2BGRA CV_GRAY2BGRA
#else
#define CV_CVTCOLOR_BGR2GREY  COLOR_BGR2GRAY
#define CV_CVTCOLOR_GREY2BGRA COLOR_GRAY2BGRA
#endif

#define K_DEFAULT_THRESHOLD 192
#define K_TRANSPARENT_ALPHA 0
#define deviant 255

#define ENSURE_NOT_NULL(x) (x != nullptr)

char * frontlayer = NULL;
char * backlayer = NULL;
char * output = NULL;
int threshold_value = K_DEFAULT_THRESHOLD;

static struct option long_options[] = {
    {"help",      no_argument,       0, 'h'},
    {"front",     required_argument, 0, 'f'},
    {"back",      required_argument, 0, 'b'},
    {"ouput",     required_argument, 0, 'o'},
    {"threshold", optional_argument, 0, 't'},
    {0, 0, 0, 0}
};

/**
 *  @brief Prints the usage message of this program.
 */
void print_usage() {
    fprintf(stderr, "Usage: hoshizora [-f front layer] [-b back layer] [-t threshold] [-o ouput]\n"
                    "                 -h To print this help\n");
}

int parse_command_line(int argc, char * const * argv) {
    int c;
    int option_index = 0;

    while (1) {
        option_index = 0;
        c = getopt_long (argc, argv, "hf:b:t:o:", long_options, &option_index);
        if (c == -1)
            break;
        switch (c) {
            case 0:
                if (long_options[option_index].flag != 0)
                    break;
                break;
            case 'h':
                print_usage();
                return 0;
            case 'o': {
                asprintf(&output, "%s", optarg);
                break;
            }
            case 'f': {
                asprintf(&frontlayer, "%s", optarg);
                break;
            }
            case 'b': {
                asprintf(&backlayer, "%s", optarg);
                break;
            }
            case 't': {
                threshold_value = atoi(optarg);
                break;
            }
            case '?':
                print_usage();
                return 0;
            default:
                abort();
        }
    }

    returnENSURE_NOT_NULL(frontlayer) &&
           ENSURE_NOT_NULL(backlayer)  &&
           ENSURE_NOT_NULL(output) &&
           threshold_value >= 0;
}

/**
 *  @brief Resize image to fit
 */
void resize(Mat &src, Mat &dest, int width, int height, int interpolation = INTER_LINEAR) {
    if (width <= 0 && height <= 0) {
        dest = src.clone();
        return;
    }
    int w,h;
    w = src.cols;
    h = src.rows;

    if (width == 0) {
        double ratio = height / double(h);
        width = w * ratio;
    } else {
        double ratio = width / double(w);
        height = h * ratio;
    }
    cv::resize(src, dest, Size(width, height), width/w, height/h, interpolation);
}

int main(int argc, const char * argv[]) {
    if (!parse_command_line(argc, (char * const *)argv)) {
        print_usage();
        exit(EXIT_FAILURE);
    }

    // Load and transform to gray scale
    Mat front = imread(frontlayer, CV_LOAD_IMAGE_GRAYSCALE);
    Mat back  = imread(backlayerCV_LOAD_IMAGE_GRAYSCALE);

    // resize image to fit
    if (front.cols > back.cols) {
        if (front.rows > back.rows) {
            if (((double)front.cols / (double)front.rows) > ((double)back.cols / (double)back.rows)) {
                resize(back, back, 0, front.rows);
            } else {
                resize(back, back, front.cols, 0);
            }
        } else {
            resize(back, back, 0, front.rows);
        }
    } else {
        if (front.rows < back.rows) {
            if (((double)front.cols / (double)front.rows) > ((double)back.cols / (double)back.rows)) {
                resize(front, front, 0, back.rows);
            } else {
                resize(front, front, back.cols, 0);
            }
        } else {
            resize(front, front, 0, back.rows);
        }
    }

    // convert colorspace into BGRA
    cvtColor(front, front, CV_CVTCOLOR_GREY2BGRA);
    cvtColor(back,  back,  CV_CVTCOLOR_GREY2BGRA);

    // for front layer
    // we remove 'white color' (above threshold)
    for (auto pixel = front.begin<Vec4b>(); pixel != front.end<Vec4b>(); pixel++) {
        if ((*pixel)[0] < threshold_value) {
            (*pixel)[2] = (*pixel)[1] = (*pixel)[0] = 0;
            (*pixel)[3] = 255;
        } else {
            (*pixel)[2] = (*pixel)[1] = (*pixel)[0] = 255;
            (*pixel)[3] = K_TRANSPARENT_ALPHA;
        }
    }

    // for back layer
    // we remove 'black color' (below threshold)
    for (auto pixel = back.begin<Vec4b>(); pixel != back.end<Vec4b>(); pixel++) {
        if ((*pixel)[0] > threshold_value) {
            (*pixel)[2] = (*pixel)[1] = (*pixel)[0] = 255;
            (*pixel)[3] = 255;
        } else {
            (*pixel)[2] = (*pixel)[1] = (*pixel)[0] = 0;
            (*pixel)[3] = K_TRANSPARENT_ALPHA;
        }
    }

    Mat result = front.clone();
    Rect roi = Rect((front.cols - back.cols) / 2, (front.rows - back.rows) / 2, back.cols, back.rows);
    Mat fusion = result(roi);
    addWeighted(fusion, 0.5, back, 0.5, 0.0, fusion);

    imwrite(output, result);

    free((void *)frontlayer);
    free((void *)backlayer);
    free((void *)output);
    return 0;
}
 

5 thoughts on “Magic image——Alpha Channel”

  1. 这么好的文章竟然没人评论∑(゚Д゚);
    其实一个月前就看过一遍的,换了n个关键字google到的这儿;
    看起来好复杂 = =;
    非cs专业小萌新,不会c++打算用python PIL库试试 ;

      1. 想把blog也做成黑白的,开灯时字体黑色,图片front layer用正常的图片,然后关灯后... .../doge脸

Leave a Reply

Your email address will not be published. Required fields are marked *

nine − 1 =