125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy/*
225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy * Copyright 2017 The Android Open Source Project
325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy *
425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy * Licensed under the Apache License, Version 2.0 (the "License");
525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy * you may not use this file except in compliance with the License.
625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy * You may obtain a copy of the License at
725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy *
825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy *      http://www.apache.org/licenses/LICENSE-2.0
925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy *
1025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy * Unless required by applicable law or agreed to in writing, software
1125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy * distributed under the License is distributed on an "AS IS" BASIS,
1225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy * See the License for the specific language governing permissions and
1425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy * limitations under the License.
1525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy */
1625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
1725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy#include <algorithm>
1825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy#include <fstream>
1925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy#include <iomanip>
2025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy#include <iostream>
2125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy#include <string>
2225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
2325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy#include <getopt.h>
2425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
2525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy#include <ui/ColorSpace.h>
2625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
2725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guyusing namespace android;
2825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guyusing namespace std;
2925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
3025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guyuint32_t gSize = 32;
3125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain GuyColorSpace gColorSpaceSrc = ColorSpace::DisplayP3();
3225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain GuyColorSpace gColorSpaceDst = ColorSpace::extendedSRGB();
3325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guystring gNameSrc = "DisplayP3";
3425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guystring gNameDst = "extendedSRGB";
3525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
3625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guystatic void printHelp() {
3725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "lutgen -d SIZE -s SOURCE -t TARGET <lut file>" << endl;
3825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << endl;
3925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "Generate a 3D LUT to convert between two color spaces." << endl;
4025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << endl;
4125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "If <lut file> ends in .inc, data is generated without the array declaration." << endl;
4225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << endl;
4325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "Options:" << endl;
4425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "  --help, -h" << endl;
4525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    print this message" << endl;
4625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "  --dimension=, -d" << endl;
4725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    the dimension of the 3D LUT. Example: 17 for a 17x17x17 LUT. 32 by default" << endl;
4825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "  --source=COLORSPACE, -s" << endl;
4925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    the source color space, see below for available names. DisplayP3 by default" << endl;
5025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "  --target=COLORSPACE, -t" << endl;
5125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    the target color space, see below for available names. extendedSRGB by default" << endl;
5225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << endl;
5325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "Colorspace names:" << endl;
5425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    sRGB" << endl;
5525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    linearSRGB" << endl;
5625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    extendedSRGB" << endl;
5725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    linearExtendedSRGB" << endl;
5825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    NTSC" << endl;
5925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    BT709" << endl;
6025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    BT2020" << endl;
6125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    AdobeRGB" << endl;
6225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    ProPhotoRGB" << endl;
6325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    DisplayP3" << endl;
6425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    DCIP3" << endl;
6525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    ACES" << endl;
6625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    cout << "    ACEScg" << endl;
6725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy}
6825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
6925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guystatic const ColorSpace findColorSpace(const string& name) {
7025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "linearSRGB") return ColorSpace::linearSRGB();
7125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "extendedSRGB") return ColorSpace::extendedSRGB();
7225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "linearExtendedSRGB") return ColorSpace::linearExtendedSRGB();
7325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "NTSC") return ColorSpace::NTSC();
7425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "BT709") return ColorSpace::BT709();
7525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "BT2020") return ColorSpace::BT2020();
7625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "AdobeRGB") return ColorSpace::AdobeRGB();
7725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "ProPhotoRGB") return ColorSpace::ProPhotoRGB();
7825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "DisplayP3") return ColorSpace::DisplayP3();
7925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "DCIP3") return ColorSpace::DCIP3();
8025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "ACES") return ColorSpace::ACES();
8125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (name == "ACEScg") return ColorSpace::ACEScg();
8225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    return ColorSpace::sRGB();
8325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy}
8425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
8525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guystatic int handleCommandLineArgments(int argc, char* argv[]) {
8625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    static constexpr const char* OPTSTR = "h:d:s:t:";
8725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    static const struct option OPTIONS[] = {
8825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            { "help",       no_argument,       0, 'h' },
8925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            { "dimension",  required_argument, 0, 'd' },
9025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            { "source",     required_argument, 0, 's' },
9125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            { "target",     required_argument, 0, 't' },
9225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            { 0, 0, 0, 0 }  // termination of the option list
9325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    };
9425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
9525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    int opt;
9625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    int index = 0;
9725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
9825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &index)) >= 0) {
9925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        string arg(optarg ? optarg : "");
10025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        switch (opt) {
10125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            default:
10225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            case 'h':
10325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                printHelp();
10425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                exit(0);
10525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                break;
10625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            case 'd':
10725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                gSize = max(2, min(stoi(arg), 256));
10825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                break;
10925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            case 's':
11025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                gNameSrc = arg;
11125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                gColorSpaceSrc = findColorSpace(arg);
11225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                break;
11325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            case 't':
11425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                gNameDst = arg;
11525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                gColorSpaceDst = findColorSpace(arg);
11625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                break;
11725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        }
11825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    }
11925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
12025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    return optind;
12125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy}
12225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
12325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guyint main(int argc, char* argv[]) {
12425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    int optionIndex = handleCommandLineArgments(argc, argv);
12525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    int numArgs = argc - optionIndex;
12625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
12725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (numArgs < 1) {
12825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        printHelp();
12925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        return 1;
13025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    }
13125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
13225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    bool isInclude = false;
13325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
13425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    string filename(argv[optionIndex]);
13525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    size_t index = filename.find_last_of('.');
13625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
13725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (index != string::npos) {
13825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        string extension(filename.substr(index + 1));
13925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        isInclude = extension == "inc";
14025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    }
14125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
14225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    ofstream outputStream(filename, ios::trunc);
14325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    if (outputStream.good()) {
14425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        auto lut = ColorSpace::createLUT(gSize, gColorSpaceSrc, gColorSpaceDst);
14525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        auto data = lut.get();
14625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
14725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        outputStream << "// generated with lutgen " << filename.c_str() << endl;
14825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        outputStream << "// 3D LUT stored as an RGB16F texture, in GL order" << endl;
14925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        outputStream << "// Size is " << gSize << "x" << gSize << "x" << gSize << endl;
15025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
15125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        string src(gNameSrc);
15225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        string dst(gNameDst);
15325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
15425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        if (!isInclude) {
15525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            transform(src.begin(), src.end(), src.begin(), ::toupper);
15625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            transform(dst.begin(), dst.end(), dst.begin(), ::toupper);
15725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
15825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            outputStream << "const size_t LUT_" << src << "_TO_" << dst << "_SIZE = " << gSize << endl;
15925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            outputStream << "const uint16_t LUT_" << src << "_TO_" << dst << "[] = {";
16025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        } else {
16125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            outputStream << "// From " << src << " to " << dst << endl;
16225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        }
16325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
16425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        for (size_t z = 0; z < gSize; z++) {
16525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            for (size_t y = 0; y < gSize; y++) {
16625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                for (size_t x = 0; x < gSize; x++) {
16725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                    if (x % 4 == 0) outputStream << endl << "    ";
16825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
16925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                    half3 rgb = half3(*data++);
17025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
17125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                    const uint16_t r = rgb.r.getBits();
17225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                    const uint16_t g = rgb.g.getBits();
17325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                    const uint16_t b = rgb.b.getBits();
17425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
17525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                    outputStream << "0x" << setfill('0') << setw(4) << hex << r << ", ";
17625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                    outputStream << "0x" << setfill('0') << setw(4) << hex << g << ", ";
17725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                    outputStream << "0x" << setfill('0') << setw(4) << hex << b << ", ";
17825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy                }
17925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            }
18025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        }
18125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
18225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        if (!isInclude) {
18325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy            outputStream << endl << "}; // end LUT" << endl;
18425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        }
18525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
18625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        outputStream << endl;
18725bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        outputStream.flush();
18825bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        outputStream.close();
18925bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    } else {
19025bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        cerr << "Could not write to file: " << filename << endl;
19125bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy        return 1;
19225bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
19325bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    }
19425bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy
19525bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy    return 0;
19625bbdcfb69d0b8fc8957b1630981632d6c9ff85dRomain Guy}
197