dcv-color-primitives 0.7.0

a library to perform image color model conversion
Documentation
#!/usr/bin/env python3

# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify,
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import sys

FIX16 = 16
FIX18 = 18
FIX16_HALF = (1 << (FIX16 - 1))
FIX_8_14 = 14
FIX_8_14_HALF = (1 << (FIX_8_14 - 1))

FIX16_MULT = (1 << FIX16)
FIX8_14_MULT = (1 << FIX_8_14)
FULL_RANGE = 255

RGB_SRC = [
    [
        [161, 24, 44, 58],
        [35, 95, 51, 205],
        [177, 30, 252, 158],
        [248, 94, 62, 28],
        [247, 51, 135, 38],
        [98, 147, 200, 127],
        [68, 103, 20, 124],
        [233, 227, 165, 0],
    ],
    [
        [251, 19, 32, 170],
        [235, 183, 25, 77],
        [146, 81, 218, 161],
        [25, 124, 96, 56],
        [22, 127, 167, 179],
        [247, 34, 40, 53],
        [164, 193, 159, 24],
        [96, 158, 17, 223],
    ],
    [
        [240, 123, 14, 108],
        [0, 105, 52, 116],
        [194, 219, 244, 47],
        [216, 254, 153, 84],
        [116, 77, 133, 68],
        [190, 96, 190, 133],
        [118, 4, 170, 115],
        [218, 145, 23, 50],
    ],
    [
        [202, 120, 126, 231],
        [42, 28, 137, 40],
        [136, 227, 210, 177],
        [254, 140, 238, 88],
        [90, 195, 170, 67],
        [125, 242, 148, 88],
        [1, 91, 190, 245],
        [31, 100, 190, 225],
    ],
    [
        [207, 49, 249, 131],
        [48, 120, 34, 82],
        [43, 145, 253, 141],
        [83, 205, 105, 44],
        [16, 9, 157, 22],
        [253, 131, 178, 148],
        [142, 236, 98, 6],
        [246, 190, 15, 213],
    ],
    [
        [72, 207, 6, 168],
        [220, 39, 6, 219],
        [244, 14, 252, 45],
        [159, 106, 17, 184],
        [222, 72, 230, 39],
        [6, 185, 30, 35],
        [101, 223, 30, 14],
        [40, 71, 16, 244],
    ],
    [
        [124, 121, 46, 190],
        [244, 206, 61, 169],
        [43, 130, 87, 247],
        [170, 10, 238, 229],
        [12, 168, 14, 220],
        [96, 60, 226, 235],
        [206, 93, 122, 117],
        [126, 168, 203, 39],
    ],
    [
        [181, 88, 248, 45],
        [65, 24, 208, 166],
        [24, 21, 151, 85],
        [60, 86, 9, 153],
        [225, 80, 156, 159],
        [210, 181, 6, 214],
        [17, 142, 255, 163],
        [189, 137, 72, 87],
    ],
]


def max_y_error(xr, xg, xb, ar, ag, ab, y_min):
    err = 0.0
    shift = (y_min << FIX16) + (1 << (FIX16 - 1))

    for red in range(256):
        y_tmp = xr * red + shift
        yf_tmp = ar * red + y_min

        for green in range(256):
            y_tmp2 = xg * green + y_tmp
            yf_tmp2 = ag * green + yf_tmp

            for blue in range(256):
                y = (xb * blue + y_tmp2) >> FIX16
                yf = ab * blue + yf_tmp2
                err = max(err, abs(yf - y))

    return err


def max_uv_error(yr, yg, zg, br, bg, bb, cr, cg, cb):
    yb = -(yr + yg)
    zr = yb
    zb = -(zr + zg)

    shift = (128 << FIX16) + (1 << (FIX16 - 1)) - 1
    u_err = 0.0
    v_err = 0.0

    for red in range(256):
        u_tmp = yr * red + shift
        v_tmp = zr * red + shift
        uf_tmp = br * red + 128
        vf_tmp = cr * red + 128

        for green in range(256):
            u_tmp2 = yg * green + u_tmp
            v_tmp2 = zg * green + v_tmp
            uf_tmp2 = bg * green + uf_tmp
            vf_tmp2 = cg * green + vf_tmp

            for blue in range(256):
                u = (yb * blue + u_tmp2) >> FIX16
                v = (zb * blue + v_tmp2) >> FIX16
                uf = bb * blue + uf_tmp2
                vf = cb * blue + vf_tmp2
                u_err = max(u_err, abs(uf - u))
                v_err = max(v_err, abs(vf - v))

    return (u_err, v_err)


if __name__ == '__main__':
    if len(sys.argv) < 3:
        print('usage: python genweights.py [601|709] [0|1]')
        exit(0)

    model = int(sys.argv[1])
    full_range = int(sys.argv[2]) == 1

    if model == 601:
        kr = 0.299
        kg = 0.587
        kb = 0.114
    elif model == 709:
        kr = 0.2126
        kg = 0.7152
        kb = 0.0722
    else:
        print('Invalid model %s' % sys.argv[1])
        exit(0)

    print('Model: %d' % model)
    print('Full range: %d' % full_range)
    print('')

    if full_range:
        Y_MIN = 0
        Y_MAX = 255
        C_MIN = 0
        C_MAX = 255
        SUFFIX = "FR"
    else:
        Y_MIN = 16
        Y_MAX = 235
        C_MIN = 16
        C_MAX = 240
        SUFFIX = ""

    Y_RANGE = Y_MAX - Y_MIN
    C_HALF = (C_MAX + C_MIN) >> 1
    C_RANGE = C_MAX - C_MIN
    Y_SCALE = (Y_RANGE / FULL_RANGE)
    C_SCALE = (C_RANGE / FULL_RANGE)

    # Forward transformation
    ar = kr
    ag = kg
    ab = kb
    br = (-kr / (2.0 * (1.0 - kb)))
    bg = (-kg / (2.0 * (1.0 - kb)))
    bb = 0.5
    cr = 0.5
    cg = (-kg / (2.0 * (1.0 - kr)))
    cb = (-kb / (2.0 * (1.0 - kr)))

    if not full_range:
        ar *= Y_SCALE
        ag *= Y_SCALE
        ab *= Y_SCALE
        br *= C_SCALE
        bg *= C_SCALE
        bb *= C_SCALE
        cr *= C_SCALE
        cg *= C_SCALE
        cb *= C_SCALE

    xr = round(FIX16_MULT * ar)
    xg = round(FIX16_MULT * ag)
    xb = round(FIX16_MULT * ab)
    y_err = max_y_error(xr, xg, xb, ar, ag, ab, Y_MIN)

    yr = round(FIX16_MULT * br)
    yg = round(FIX16_MULT * bg)
    zg = round(FIX16_MULT * cg)
    diff = -32767 - (yr + yg)
    if diff > 0:
        # Prevent overflow
        # (decide which variable between yr and yg should be incremented)
        uv_err = max_uv_error(yr + diff, yg,
                              zg, br, bg, bb, cr, cg, cb)
        uv2_err = max_uv_error(yr, yg + diff, zg,
                               br, bg, bb, cr, cg, cb)

        if max(*uv_err) <= max(*uv2_err):
            yr += diff
        else:
            yg += diff
            uv_err = uv2_err
    else:
        uv_err = max_uv_error(yr, yg, zg, br, bg, bb, cr, cg, cb)

    print('Error: y=%.5f, u=%.5f, v=%.5f' % (y_err, *uv_err))
    print('')

    print('// Coefficient table for %s%s' %
          (model, " (full range)" if full_range else ""))
    print('pub const XR_%d%s: i32 = %d;' % (model, SUFFIX, xr))
    print('pub const XG_%d%s: i32 = %d;' % (model, SUFFIX, xg))
    print('pub const XB_%d%s: i32 = %d;' % (model, SUFFIX, xb))
    print('pub const YR_%d%s: i32 = %d;' % (model, SUFFIX, yr))
    print('pub const YG_%d%s: i32 = %d;' % (model, SUFFIX, yg))
    print('pub const ZG_%d%s: i32 = %d;' % (model, SUFFIX, zg))
    print('')

    # Inverse transformation
    ikb = 1.0 - kb
    ikr = 1.0 - kr
    y_scale = 1 / Y_SCALE

    rz = 2.0 * ikr / C_SCALE
    gy = (2.0 * ikb * kb) / (C_SCALE * kg)
    gz = (2.0 * ikr * kr) / (C_SCALE * kg)
    by = 2.0 * ikb / C_SCALE

    s = int(FIX8_14_MULT * y_scale + 0.5)
    rz = int(FIX8_14_MULT * rz + 0.5)
    gy = int(FIX8_14_MULT * gy + 0.5)
    gz = int(FIX8_14_MULT * gz + 0.5)
    by = int(FIX8_14_MULT * by + 0.5)

    rw = rz * C_HALF + s * Y_MIN - FIX_8_14_HALF
    gw = (gy * C_HALF) + (gz * C_HALF) - (s * Y_MIN) + FIX_8_14_HALF
    bw = s * Y_MIN + by * C_HALF - FIX_8_14_HALF

    print('pub const XXYM_%d%s: i32 = %d;' % (model, SUFFIX, s))
    print('pub const RCRM_%d%s: i32 = %d;' % (model, SUFFIX, rz))
    print('pub const GCRM_%d%s: i32 = %d;' % (model, SUFFIX, gz))
    print('pub const GCBM_%d%s: i32 = %d;' % (model, SUFFIX, gy))
    print('pub const BCBM_%d%s: i32 = %d;' % (model, SUFFIX, by))
    print('pub const RN_%d%s: i32 = %d;' % (model, SUFFIX, rw >> 8))
    print('pub const GP_%d%s: i32 = %d;' % (model, SUFFIX, gw >> 8))
    print('pub const BN_%d%s: i32 = %d;' % (model, SUFFIX, bw >> 8))

    # Reference data for regression tests
    yb = -(yr + yg)
    zr = yb
    zb = -(zr + zg)
    uv_shift = (128 << FIX16) + (1 << (FIX16 - 1)) - 1
    uv2_shift = (128 << FIX18) + (1 << (FIX18 - 1)) - 1
    y_shift = (Y_MIN << FIX16) + (1 << (FIX16 - 1))

    print('const Y_BT%d%s_REF: &[&[u8]] = &[' % (model, SUFFIX))
    for row in RGB_SRC:
        ys = [(xr * r + xg * g + xb * b + y_shift) >> FIX16
              for [r, g, b, a] in row]
        print('    &%s,' % ys)
    print('];\n')

    print('const CB_BT%d%s_REF: &[&[u8]] = &[' % (model, SUFFIX))
    for row in RGB_SRC:
        us = [(yr * r + yg * g + yb * b + uv_shift) >> FIX16
              for [r, g, b, a] in row]
        print('    &%s,' % us)
    print('];\n')

    print('const CR_BT%d%s_REF: &[&[u8]] = &[' % (model, SUFFIX))
    for row in RGB_SRC:
        vs = [(zr * r + zg * g + zb * b + uv_shift) >> FIX16
              for [r, g, b, a] in row]
        print('    &%s,' % vs)
    print('];\n')

    # The same but subsampled
    print('const CB2_BT%d%s_REF: &[&[u8]] = &[' % (model, SUFFIX))
    for j in range(4):
        us = []
        for i in range(4):
            ii = 2 * i
            ij = 2 * j

            r = RGB_SRC[ij][ii][0] + RGB_SRC[ij + 1][ii][0] + \
                RGB_SRC[ij][ii + 1][0] + RGB_SRC[ij + 1][ii + 1][0]
            g = RGB_SRC[ij][ii][1] + RGB_SRC[ij + 1][ii][1] + \
                RGB_SRC[ij][ii + 1][1] + RGB_SRC[ij + 1][ii + 1][1]
            b = RGB_SRC[ij][ii][2] + RGB_SRC[ij + 1][ii][2] + \
                RGB_SRC[ij][ii + 1][2] + RGB_SRC[ij + 1][ii + 1][2]

            us.append((yr * r + yg * g + yb * b + uv2_shift) >> FIX18)

        print('    &%s,' % us)
    print('];\n')

    print('const CR2_BT%d%s_REF: &[&[u8]] = &[' % (model, SUFFIX))
    for j in range(4):
        us = []
        for i in range(4):
            ii = 2 * i
            ij = 2 * j

            r = RGB_SRC[ij][ii][0] + RGB_SRC[ij + 1][ii][0] + \
                RGB_SRC[ij][ii + 1][0] + RGB_SRC[ij + 1][ii + 1][0]
            g = RGB_SRC[ij][ii][1] + RGB_SRC[ij + 1][ii][1] + \
                RGB_SRC[ij][ii + 1][1] + RGB_SRC[ij + 1][ii + 1][1]
            b = RGB_SRC[ij][ii][2] + RGB_SRC[ij + 1][ii][2] + \
                RGB_SRC[ij][ii + 1][2] + RGB_SRC[ij + 1][ii + 1][2]

            us.append((zr * r + zg * g + zb * b + uv2_shift) >> FIX18)

        print('    &%s,' % us)
    print('];')