Exemplo n.º 1
0
def create_testcharts(overwrite=False):
    min_bcc_steps, max_bcc_steps = 7, 11
    # Profile, amount of dark region emphasis
    precond = {
        "eRGBv2": (ICCP.ICCProfile("eciRGB_v2.icc").fileName, 1.0),
        "aRGB": (config.get_data_path("ref/ClayRGB1998.icm"), 1.6),
        "Rec709_Gamma22":
        (config.get_data_path("ref/Rec709_Gamma22.icm"), 1.6),
        "sRGB": (config.get_data_path("ref/sRGB.icm"), 1.6)
    }
    worker = Worker()
    targen = get_argyll_util("targen")
    for bcc_steps in xrange(min_bcc_steps, max_bcc_steps + 1):
        single_channel = bcc_steps * 4 - 3
        gray_channel = single_channel * 3 - 2
        total = config.get_total_patches(4, 4, single_channel, gray_channel,
                                         bcc_steps, bcc_steps, 0)
        for name, (filename, demphasis) in precond.iteritems():
            cwd = os.path.join(root, meta.name, "ti1")
            outname = "d3-e4-s%i-g%i-m0-f%i-c%s" % (single_channel,
                                                    gray_channel, total, name)
            if (not os.path.isfile(os.path.join(cwd, outname + ".ti1"))
                    or overwrite):
                result = worker.exec_cmd(targen, [
                    "-v", "-d3", "-e4",
                    "-s%i" % single_channel,
                    "-g%i" % gray_channel, "-m0",
                    "-f%i" % total, "-G", "-c" + filename,
                    "-V%.1f" % demphasis, outname
                ],
                                         working_dir=cwd,
                                         sessionlogfile=sys.stdout)
                if isinstance(result, Exception):
                    print result
        worker.wrapup(False)
Exemplo n.º 2
0
def profileinfo(profile):
    if not isinstance(profile, ICCP.ICCProfile):
        profile = ICCP.ICCProfile(profile)
    # Attributes
    print "Size:", profile.size, "Bytes (%.1f KB)" % (profile.size / 1024.0)
    print "Preferred CMM:", profile.preferredCMM
    print "ICC version:", profile.version
    print "Class:", profile.profileClass
    print "Colorspace:", profile.colorSpace
    print "PCS:", profile.connectionColorSpace
    print "Date/Time:", strftime("%Y-%m-%d %H:%M:%S",
                                 profile.dateTime + (0, 0, -1))
    print "Platform:", profile.platform
    print "Embedded:", profile.embedded
    print "Independent:", profile.independent
    print "Device:"
    for key in ("manufacturer", "model"):
        profile.device[key] = binascii.hexlify(profile.device[key]).upper()
    prettyprint(profile.device)
    print "Rendering Intent:", profile.intent
    print "Illuminant:", " ".join(
        str(n * 100) for n in profile.illuminant.values())
    print "Creator:", profile.creator
    print "ID:", binascii.hexlify(profile.ID).upper()
    # Tags
    print "Description:", profile.getDescription()
    print "Copyright:", profile.getCopyright()
    if "dmnd" in profile.tags:
        print "Device Manufacturer Description:",
        print profile.getDeviceManufacturerDescription()
    if "dmdd" in profile.tags:
        print "Device Model Description:", profile.getDeviceModelDescription()
    if "vued" in profile.tags:
        print "Viewing Conditions Description:",
        print profile.getViewingConditionsDescription()
    wtpt_profile_norm = tuple(n * 100 for n in profile.tags.wtpt.values())
    if "chad" in profile.tags:
        # undo chromatic adaption of profile whitepoint
        X, Y, Z = wtpt_profile_norm
        M = colormath.Matrix3x3(profile.tags.chad).inverted()
        XR = X * M[0][0] + Y * M[0][1] + Z * M[0][2]
        YR = X * M[1][0] + Y * M[1][1] + Z * M[1][2]
        ZR = X * M[2][0] + Y * M[2][1] + Z * M[2][2]
        wtpt_profile_norm = tuple((n / YR) * 100.0 for n in (XR, YR, ZR))
    if "lumi" in profile.tags and isinstance(profile.tags.lumi, ICCP.XYZType):
        print "Luminance:", profile.tags.lumi.Y
    print "Actual Whitepoint XYZ:", " ".join(str(n) for n in wtpt_profile_norm)
    print "Correlated Color Temperature:", colormath.XYZ2CCT(
        *wtpt_profile_norm)
def set_profile_desc_to_basename_sans_ext(profile):
	if isinstance(profile, basestring):
		profile = iccp.ICCProfile(profile)
	if isinstance(profile, iccp.ICCProfile):
		name = os.path.splitext(os.path.basename(profile.fileName))[0]
		if isinstance(profile.tags.desc, iccp.TextDescriptionType):
			profile.tags.desc.ASCII = name.encode('ASCII', 'asciize')
			profile.tags.desc.Unicode = name
			profile.tags.desc.Macintosh = name
		else:
			profile.tags.desc = iccp.MultiLocalizedUnicodeType()
			profile.tags.desc.add_localized_string('en', 'US', name)
		profile.write()
	else:
		for item in profile:
			set_profile_desc_to_basename_sans_ext(item)
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from DisplayCAL import ICCProfile as iccp
from DisplayCAL.defaultpaths import iccprofiles, iccprofiles_home
from DisplayCAL.safe_print import safe_print

for p in set(iccprofiles_home + iccprofiles):
    if os.path.isdir(p):
        for f in os.listdir(p):
            try:
                profile = iccp.ICCProfile(os.path.join(p, f))
            except:
                pass
            else:
                if profile.profileClass == "spac":
                    safe_print(f)
                    safe_print("ICC Version:", profile.version)
                    safe_print("Color space:", profile.colorSpace)
                    safe_print("Connection color space:",
                               profile.connectionColorSpace)
                    safe_print("")
Exemplo n.º 5
0
def update_preset(name):
    print "Preset name:", name
    pth = config.get_data_path("presets/%s.icc" % name)
    if not pth:
        print "ERROR: Preset not found"
        return False
    print "Path:", pth
    with open(
            os.path.join(os.path.dirname(__file__), "..", "misc", "ti3",
                         "%s.ti3" % name), "rb") as f:
        ti3 = f.read()
    prof = ICCP.ICCProfile(pth)
    if prof.tags.targ != ti3:
        print "Updating 'targ'..."
        prof.tags.targ = ICCP.TextType("text\0\0\0\0%s\0" % ti3, "targ")
    options_dispcal, options_colprof = worker.get_options_from_profile(prof)
    trc_a2b = {"240": -240, "709": -709, "l": -3, "s": -2.4}
    t_a2b = {"t": colormath.CIEDCCT2XYZ, "T": colormath.planckianCT2XYZ}
    trc = None
    for option in options_dispcal:
        if option[0] in ("g", "G"):
            trc = trc_a2b.get(option[1:])
            if not trc:
                try:
                    trc = float(option[1:])
                except ValueError:
                    trc = False
                    print "Invalid dispcal -g parameter:", option[1:]
            if trc:
                print "dispcal -%s parameter:" % option[0], option[1:]
                print "Updating tone response curves..."
                for chan in ("r", "g", "b"):
                    prof.tags["%sTRC" % chan].set_trc(trc, 256)
                print "Transfer function:", prof.tags[
                    "%sTRC" % chan].get_transfer_function()[0][0]
        elif option[0] in ("t", "T") and option[1:]:
            print "dispcal -t parameter:", option[1:]
            print "Updating white point..."
            (prof.tags.wtpt.X, prof.tags.wtpt.Y,
             prof.tags.wtpt.Z) = t_a2b[option[0]](float(option[1:]))
        elif option[0] == "w":
            print "dispcal -w parameter:", option[1:]
            x, y = [float(v) for v in option[1:].split(",")]
            print "Updating white point..."
            (prof.tags.wtpt.X, prof.tags.wtpt.Y,
             prof.tags.wtpt.Z) = colormath.xyY2XYZ(x, y)
        elif option[0] in ("t", "T"):
            print "Updating white point..."
            (prof.tags.wtpt.X, prof.tags.wtpt.Y,
             prof.tags.wtpt.Z) = colormath.get_whitepoint("D65")
    for option in options_colprof:
        if option[0] == "M":
            print "Updating device model description..."
            prof.setDeviceModelDescription(option[2:].strip('"'))
    if "CIED" in prof.tags:
        print "Removing 'CIED'..."
        del prof.tags["CIED"]
    if "DevD" in prof.tags:
        print "Removing 'DevD'..."
        del prof.tags["DevD"]
    if "clrt" in prof.tags:
        print "Removing 'clrt'..."
        del prof.tags["clrt"]
    print "Setting RGB matrix column tags to reference values..."
    prof.tags.rXYZ = ref.tags.rXYZ
    prof.tags.gXYZ = ref.tags.gXYZ
    prof.tags.bXYZ = ref.tags.bXYZ
    print "Updating profile ID..."
    prof.calculateID()
    prof.write()
    print ""
    return True
Exemplo n.º 6
0
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import glob
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from DisplayCAL import ICCProfile as ICCP, colormath, config, worker

config.initcfg()
srgb = config.get_data_path("ref/sRGB.icm")
if not srgb:
    raise OSError("File not found: ref/sRGB.icm")
ref = ICCP.ICCProfile(srgb)
print "sRGB:", ref.fileName


def update_preset(name):
    print "Preset name:", name
    pth = config.get_data_path("presets/%s.icc" % name)
    if not pth:
        print "ERROR: Preset not found"
        return False
    print "Path:", pth
    with open(
            os.path.join(os.path.dirname(__file__), "..", "misc", "ti3",
                         "%s.ti3" % name), "rb") as f:
        ti3 = f.read()
    prof = ICCP.ICCProfile(pth)
Exemplo n.º 7
0
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from DisplayCAL import ICCProfile as ICCP, xrandr
from DisplayCAL.safe_print import safe_print
from DisplayCAL.RealDisplaySizeMM import RealDisplaySizeMM as RDSMM

for i in xrange(5):
    # Show ICC info for first five screens / outputs
    try:
        x_icc_c = xrandr.get_atom(
            "_ICC_PROFILE" if i < 1 else "_ICC_PROFILE_%i" % i)
    except ValueError:
        x_icc_c = None
    if x_icc_c:
        safe_print("Root window %s" %
                   ("_ICC_PROFILE" if i < 1 else "_ICC_PROFILE_%i" % i))
        x_icc = ICCP.ICCProfile("".join(chr(n) for n in x_icc_c))
        safe_print("Description:", x_icc.getDescription())
        safe_print("Checksum ID:", hexlify(x_icc.calculateID()))
        safe_print("")
    try:
        xrr_icc_c = xrandr.get_output_property(i, "_ICC_PROFILE")
    except ValueError:
        xrr_icc_c = None
    if xrr_icc_c:
        safe_print("XRandR Output %i _ICC_PROFILE:" % i)
        xrr_icc = ICCP.ICCProfile("".join(chr(n) for n in xrr_icc_c))
        safe_print("Description:", xrr_icc.getDescription())
        safe_print("Checksum ID:", hexlify(xrr_icc.calculateID()))
        safe_print("")
def main(icc_profile_filename,
         target_whitepoint=None,
         gamma=2.2,
         skip_cal=False):
    profile = ICCP.ICCProfile(icc_profile_filename)

    worker = Worker()

    if target_whitepoint:
        intent = "a"
    else:
        intent = "r"

    gamma = float(gamma)

    num_cal_entries = 4096
    cal_entry_max = num_cal_entries - 1.0

    ogamma = {
        -2.4: "sRGB",
        -3.0: "LStar",
        -2084: "SMPTE2084",
        -709: "BT709",
        -240: "SMPTE240M",
        -601: "BT601"
    }.get(gamma, gamma)
    owtpt = target_whitepoint and ".%s" % target_whitepoint or ""

    filename, ext = os.path.splitext(icc_profile_filename)

    # Get existing calibration CGATS from profile
    existing_cgats = argyll_cgats.extract_cal_from_profile(
        profile, raise_on_missing_cal=False)

    # Enabling this will do a linear blend below interpolation threshold, which
    # is probably not what we want - rather, the original cal should blend into
    # the linear portion
    applycal = False

    applycal_inverse = not filter(
        lambda tagname: tagname.startswith("A2B") or tagname.startswith("B2A"),
        profile.tags)
    print "Use applycal to apply cal?", applycal
    print "Use applycal to apply inverse cal?", applycal_inverse
    print "Ensuring 256 entry TRC tags"
    _applycal_bug_workaround(profile)
    if (applycal or applycal_inverse) and existing_cgats:
        print "Writing TMP profile for applycal"
        profile.write(filename + ".tmp" + ext)
    if applycal and existing_cgats:
        # Apply cal
        existing_cgats.write(filename + ".tmp.cal")
        result = worker.exec_cmd(get_argyll_util("applycal"), [
            "-v", filename + ".tmp.cal", filename + ".tmp" + ext,
            filename + ".calapplied" + ext
        ],
                                 capture_output=True,
                                 log_output=True)
        if not result and not os.path.isfile(out_filename):
            raise Exception("applycal returned a non-zero exit code")
        elif isinstance(result, Exception):
            raise result
        calapplied = ICCP.ICCProfile(filename + ".calapplied" + ext)
    else:
        calapplied = profile

    if target_whitepoint:
        try:
            target_whitepoint = float(target_whitepoint)
        except ValueError:
            pass
        target_whitepoint = cm.get_whitepoint(target_whitepoint)

        # target_whitepoint = cm.adapt(*target_whitepoint,
        # whitepoint_source=profile.tags.wtpt.ir.values())

        logfiles = sys.stdout

        # Lookup scaled down white XYZ
        logfiles.write("Looking for solution...\n")
        for n in xrange(9):
            XYZscaled = []
            for i in xrange(2001):
                XYZscaled.append([
                    v * (1 - (n * 2001 + i) / 20000.0)
                    for v in target_whitepoint
                ])
            RGBscaled = worker.xicclu(profile,
                                      XYZscaled,
                                      intent,
                                      "if",
                                      pcs="x",
                                      get_clip=True)
            # Find point at which it no longer clips
            XYZwscaled = None
            for i, RGBclip in enumerate(RGBscaled):
                if RGBclip[3] is True or max(RGBclip[:3]) > 1:
                    # Clipped, skip
                    continue
                # Found
                XYZwscaled = XYZscaled[i]
                logfiles.write("Solution found at index %i "
                               "(step size %f)\n" % (i, 1 / 2000.0))
                logfiles.write("RGB white %6.4f %6.4f %6.4f\n" %
                               tuple(RGBclip[:3]))
                logfiles.write("XYZ white %6.4f %6.4f %6.4f, "
                               "CCT %.1f K\n" %
                               tuple(XYZscaled[i] + [cm.XYZ2CCT(*XYZwscaled)]))
                break
            else:
                if n == 8:
                    break
            if XYZwscaled:
                # Found solution
                break
        if not XYZwscaled:
            raise Exception("No solution found in %i "
                            "iterations with %i steps" % (n, i))
        target_whitepoint = XYZwscaled
        del RGBscaled

    if not applycal or applycal_inverse:
        ccal = get_cal(num_cal_entries,
                       target_whitepoint,
                       gamma,
                       profile,
                       intent,
                       "if",
                       slope_limit=0)

    out_filename = filename + " %s%s" % (
        target_whitepoint and "%s " % owtpt[1:] or "", ogamma) + ext

    if target_whitepoint is False:  # NEVER
        # Apply inverse CAL with PCS white
        main(icc_profile_filename, gamma=gamma, skip_cal=True)
    else:
        # Generate inverse calibration

        # Apply inverse CAL and write output file
        if not applycal_inverse:
            # Use our own code

            TRC = []

            seen = []

            for tagname in ("A2B0", "A2B1", "A2B2", "B2A0", "B2A1", "B2A2",
                            "rTRC", "gTRC", "bTRC"):
                if not tagname in profile.tags:
                    continue
                print tagname
                if profile.tags[tagname] in seen:
                    print "Already seen"
                    continue
                seen.append(profile.tags[tagname])
                if tagname.startswith("A2B"):
                    # Apply calibration to input curves
                    cal = get_cal(num_cal_entries, None, gamma, profile,
                                  intent, "if")
                    interp_i = get_interp(cal, True)
                    entries = profile.tags[tagname].input
                elif tagname.startswith("B2A"):
                    # Apply inverse calibration to output curves
                    if profile.tags[tagname].clut_grid_steps <= 9:
                        # Low quality. Skip.
                        print "Low quality, skipping"
                        continue
                    cal = get_cal(num_cal_entries, None, gamma, profile,
                                  intent, "b")
                    interp_i = get_interp(cal, True)
                    entries = profile.tags[tagname].output
                else:
                    entries = profile.tags[tagname]
                    TRC.append(entries[:])
                    num_entries = len(entries)
                    j = "rgb".index(tagname[0])
                    cal = get_cal(num_cal_entries, None, gamma, profile,
                                  intent, "if", "r")
                    interp_i = get_interp(cal, True)
                    cinterp = cm.Interp([
                        interp_i[j](i / (num_entries - 1.0))
                        for i in xrange(num_entries)
                    ],
                                        entries,
                                        use_numpy=True)
                    entries[:] = [
                        cinterp(i / (num_entries - 1.0))
                        for i in xrange(num_entries)
                    ]
                    continue
                for j in xrange(3):
                    num_entries = len(entries[j])
                    if tagname.startswith("A2B"):
                        cinterp = cm.Interp([
                            interp_i[j](i / (num_entries - 1.0))
                            for i in xrange(num_entries)
                        ], [v / 65535. for v in entries[j]],
                                            use_numpy=True)
                    elif tagname.startswith("B2A"):
                        rinterp = cm.Interp([v / 65535. for v in entries[j]], [
                            i / (num_entries - 1.0)
                            for i in xrange(num_entries)
                        ],
                                            use_numpy=True)
                        cinterp = cm.Interp([
                            rinterp(i / (num_entries - 1.0))
                            for i in xrange(num_entries)
                        ], [
                            interp_i[j](i / (num_entries - 1.0))
                            for i in xrange(num_entries)
                        ],
                                            use_numpy=True)
                    entries[j] = []
                    num_entries = max(num_entries, 256)
                    for i in xrange(num_entries):
                        entries[j].append(
                            min(
                                max(
                                    cinterp(i / (num_entries - 1.0)) * 65535,
                                    0), 65535))

            # Check for identical initial TRC tags, and force them identical again
            if TRC and TRC.count(TRC[0]) == 3:
                print "Forcing identical TRC tags"
                for channel in "rb":
                    profile.tags[channel + "TRC"] = profile.tags.gTRC

        elif existing_cgats:
            # Use Argyll applycal
            # XXX: Want to derive different cals for cLUT and TRC tags.
            # Not possible with applycal unless applying cals separately and
            # combining the profile parts later?

            # Get inverse calibration
            interp_i = get_interp(ccal, True)
            ical = []
            # Argyll can only deal with 256 cal entries
            for i in xrange(256):
                ical.append([cinterp(i / 255.) for cinterp in interp_i])

            # Write inverse CAL
            icgats = cgats_header
            for i, (R, G, B) in enumerate(ical):
                icgats += "%.7f %.7f %.7f %.7f\n" % (i / 255., R, G, B)
            icgats += "END_DATA\n"
            ical_filename = icc_profile_filename + owtpt + ".%s.inverse.cal" % ogamma
            with open(ical_filename, "wb") as f:
                f.write(icgats)

            result = worker.exec_cmd(
                get_argyll_util("applycal"),
                ["-v", ical_filename, filename + ".tmp" + ext, out_filename],
                capture_output=True,
                log_output=True)
            if not result and not os.path.isfile(out_filename):
                raise Exception("applycal returned a non-zero exit code")
            elif isinstance(result, Exception):
                raise result

            profile = ICCP.ICCProfile(out_filename)

    out_profile = profile

    if not skip_cal:
        # Improve existing calibration with new calibration

        if applycal:
            ccal = get_cal(256,
                           target_whitepoint,
                           gamma,
                           calapplied,
                           intent,
                           "if",
                           slope_limit=0)
            num_cal_entries = len(ccal)
        else:
            if not existing_cgats:
                existing_cgats = CGATS.CGATS(
                    config.get_data_path("linear.cal"))

            num_cal_entries = len(existing_cgats[0].DATA)

        cal_entry_max = num_cal_entries - 1.0

        if not applycal:
            interp = get_interp(ccal, False, True)

            # Create CAL diff
            cgats = cgats_header
            for i in xrange(num_cal_entries):
                RGB = [cinterp(i / cal_entry_max) for cinterp in interp]
                R, G, B = (min(max(v, 0), 1) for v in RGB)
                cgats += "%.7f %.7f %.7f %.7f\n" % (i / cal_entry_max, R, G, B)
            cgats += "END_DATA\n"
            with open(icc_profile_filename + owtpt + ".%s.diff.cal" % ogamma,
                      "wb") as f:
                f.write(cgats)

            cgats_cal_interp = []
            for i in xrange(3):
                cgats_cal_interp.append(
                    cm.Interp(
                        [v / cal_entry_max for v in xrange(num_cal_entries)],
                        []))
            for i, row in existing_cgats[0].DATA.iteritems():
                for j, channel in enumerate("RGB"):
                    cgats_cal_interp[j].fp.append(row["RGB_" + channel])

        # Create CAL
        cgats = cgats_header
        for i in xrange(num_cal_entries):
            if applycal:
                RGB = [ccal[i][j] for j in xrange(3)]
            else:
                RGB = [
                    cgats_cal_interp[j](cinterp(i / cal_entry_max))
                    for j, cinterp in enumerate(interp)
                ]
            R, G, B = (min(max(v, 0), 1) for v in RGB)
            cgats += "%.7f %.7f %.7f %.7f\n" % (i / cal_entry_max, R, G, B)
        cgats += "END_DATA\n"
        with open(icc_profile_filename + owtpt + ".%s.cal" % ogamma,
                  "wb") as f:
            f.write(cgats)

        # Add CAL as vcgt to profile
        out_profile.tags.vcgt = argyll_cgats.cal_to_vcgt(cgats)

        if target_whitepoint:
            # Update wtpt tag
            (out_profile.tags.wtpt.X, out_profile.tags.wtpt.Y,
             out_profile.tags.wtpt.Z) = target_whitepoint

    # Write updated profile
    out_profile.setDescription(
        out_profile.getDescription() + " %s%s" %
        (target_whitepoint and "%s " % owtpt[1:] or "", ogamma))
    out_profile.calculateID()
    out_profile.write(out_filename)
def main(*args, **kwargs):
    # Parse arguments
    cal_only = kwargs.get("--cal-only", CAL_ONLY)
    state = None
    xy = None
    profile = None
    outfilename = None
    for i, arg in enumerate(args):
        if arg == "-t":
            state = "COLORTEMP_DAYLIGHT"
        elif arg == "-T":
            state = "COLORTEMP_BLACKBODY"
        elif arg.startswith("-t") or arg.startswith("-T") or state in (
                "COLORTEMP_DAYLIGHT", "COLORTEMP_BLACKBODY"):
            if state in ("COLORTEMP_DAYLIGHT", "COLORTEMP_BLACKBODY"):
                ctstr = arg
            else:
                ctstr = arg[2:]
            try:
                ct = float(ctstr)
            except ValueError:
                raise Invalid("Invalid color temperature %s" % ctstr)
            if arg.startswith("-t") or state == "COLORTEMP_DAYLIGHT":
                xy = cm.CIEDCCT2xyY(ct)
                if not xy:
                    raise Invalid(
                        "Daylight color temperature %i out of range" % ct)
            else:
                xy = cm.planckianCT2xyY(ct)
                if not xy:
                    raise Invalid(
                        "Blackbody color temperature %i out of range" % ct)
            state = None
        elif arg == "-w":
            state = "CHROMATICITY"
        elif arg.startswith("-w") or state == "CHROMATICITY":
            if state == "CHROMATICITY":
                xystr = arg
            else:
                xystr = arg[2:]
            xy = xystr.split(",")
            if len(xy) != 2:
                raise Invalid("Invalid chromaticity: %s" % xystr)
            try:
                xy = [float(v) for v in xy]
            except ValueError:
                raise Invalid("Invalid chromaticity %s" % xystr)
            state = None
        elif os.path.isfile(arg) and i < len(args) - 1:
            safe_print("Reading profile:", arg)
            profile = ICCP.ICCProfile(arg)
        else:
            outfilename = os.path.abspath(arg)
    if not xy or not outfilename:
        raise Invalid(
            "Usage: %s [-t temp | -T temp | -w x,y] [--cal-only] [inprofile] outfilename"
            % os.path.basename(__file__))
    if not profile:
        safe_print("Reading display profile")
        profile = ICCP.get_display_profile()
    # Setup
    config.initcfg()
    lang.init()
    w = worker.Worker()
    fn = w.change_display_profile_cal_whitepoint
    args = profile, xy[0], xy[1], outfilename, cal_only, USE_COLLINK
    # Process
    if CAL_ONLY:
        fn(*args)
    else:
        app = BaseApp(0)
        app.TopWindow = wx.Frame(None)
        w.start(lambda result: app.ExitMainLoop(),
                fn,
                wargs=args,
                progress_msg=lang.getstr("create_profile"))
        app.MainLoop()