lcms2.py (3942B)
1 #!/usr/bin/env python3 2 # Copyright (c) the JPEG XL Project Authors. All rights reserved. 3 # 4 # Use of this source code is governed by a BSD-style 5 # license that can be found in the LICENSE file. 6 7 import ctypes 8 from numpy.ctypeslib import ndpointer 9 import numpy 10 import os 11 import platform 12 13 IS_OSX = (platform.system() == "Darwin") 14 15 default_libcms2_lib_path = ["liblcms2.so.2", "liblcms2.2.dylib"][IS_OSX] 16 lcms2_lib_path = os.getenv("LCMS2_LIB_PATH", default_libcms2_lib_path) 17 lcms2_lib = ctypes.cdll.LoadLibrary(lcms2_lib_path) 18 19 native_open_profile = lcms2_lib.cmsOpenProfileFromMem 20 native_open_profile.restype = ctypes.c_void_p 21 native_open_profile.argtypes = [ 22 ctypes.c_char_p, # MemPtr 23 ctypes.c_size_t # dwSize 24 ] 25 26 native_close_profile = lcms2_lib.cmsCloseProfile 27 native_close_profile.restype = ctypes.c_int 28 native_close_profile.argtypes = [ 29 ctypes.c_void_p # hProfile 30 ] 31 32 native_create_transform = lcms2_lib.cmsCreateTransform 33 native_create_transform.restype = ctypes.c_void_p 34 native_create_transform.argtypes = [ 35 ctypes.c_void_p, # Input 36 ctypes.c_uint32, # InputFormat 37 ctypes.c_void_p, # Output 38 ctypes.c_uint32, # OutputFormat 39 ctypes.c_uint32, # Intent 40 ctypes.c_uint32 # dwFlags 41 ] 42 43 native_delete_transform = lcms2_lib.cmsDeleteTransform 44 native_delete_transform.restype = None 45 native_delete_transform.argtypes = [ 46 ctypes.c_void_p # hTransform 47 ] 48 49 native_do_transform = lcms2_lib.cmsDoTransform 50 native_do_transform.restype = None 51 native_do_transform.argtypes = [ 52 ctypes.c_void_p, # Transform 53 ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), # InputBuffer 54 ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), # OutputBuffer 55 ctypes.c_uint32 # Size 56 ] 57 58 59 def make_format( 60 bytes_per_sample=4, # float32 61 num_channels=3, # RGB or XYZ 62 extra_channels=0, 63 swap_channels=0, 64 swap_endiannes=0, 65 planar=0, 66 flavor=0, 67 swap_first=0, 68 unused=0, 69 pixel_type=4, # RGB 70 optimized=0, 71 floating_point=1): 72 values = [bytes_per_sample, num_channels, extra_channels, swap_channels, 73 swap_endiannes, planar, flavor, swap_first, unused, pixel_type, 74 optimized, floating_point] 75 bit_width = [3, 4, 3, 1, 1, 1, 1, 1, 1, 5, 1, 1] 76 result = 0 77 shift = 0 78 for i in range(len(bit_width)): 79 result += values[i] << shift 80 shift += bit_width[i] 81 return result 82 83 84 def convert_pixels(from_icc, to_icc, from_pixels): 85 from_icc = bytearray(from_icc) 86 to_icc = bytearray(to_icc) 87 88 if len(from_pixels.shape) != 3 or from_pixels.shape[2] != 3: 89 raise ValueError("Only WxHx3 shapes are supported") 90 from_pixels_plain = from_pixels.ravel().astype(numpy.float64) 91 num_pixels = len(from_pixels_plain) // 3 92 to_pixels_plain = numpy.empty(num_pixels * 3, dtype=numpy.float64) 93 94 from_icc = (ctypes.c_char * len(from_icc)).from_buffer(from_icc) 95 from_profile = native_open_profile( 96 ctypes.cast(ctypes.pointer(from_icc), ctypes.c_char_p), len(from_icc)) 97 98 to_icc = (ctypes.c_char * len(to_icc)).from_buffer(to_icc) 99 to_profile = native_open_profile( 100 ctypes.cast(ctypes.pointer(to_icc), ctypes.c_char_p), len(to_icc)) 101 102 # bytes_per_sample=0 actually means 8 bytes (but there are just 3 bits to 103 # encode the length of sample) 104 format_rgb_f64 = make_format(bytes_per_sample=0) 105 intent = 0 # INTENT_PERCEPTUAL 106 flags = 0 # default; no "no-optimization" 107 transform = native_create_transform( 108 from_profile, format_rgb_f64, to_profile, format_rgb_f64, intent, flags) 109 110 native_do_transform( 111 transform, from_pixels_plain, to_pixels_plain, num_pixels) 112 113 native_delete_transform(transform) 114 native_close_profile(to_profile) 115 native_close_profile(from_profile) 116 117 # Return same shape and size as input 118 return to_pixels_plain.reshape(from_pixels.shape).astype(from_pixels.dtype) 119 120 if __name__ == '__main__': 121 raise Exception("Not an executable")