#!/usr/bin/env python3

# python3 -m pip install -U tifffile[all]
from tifffile import (imread, imwrite, TiffFile)

filename_input = "xyz-16bits.tif"
filename_output = "out.tif"


def extract_and_convert(filename_input):

	with TiffFile(filename_input) as tif:
		for page_index, page in enumerate(tif.pages):
			for line_index, line in enumerate(page.asarray()):
				for pixel_index, pixel in enumerate(line):

					print(f"==== Pixel {page_index}{line_index}{pixel_index} =====")

					print(f"Read 16 bits\t:\t", end="")
					print(f"X = 0x{pixel[0]:04x}\t\tY = 0x{pixel[1]:04x}\t\tZ = 0x{pixel[2]:04x}")

					"""
					to divide by the maximum value of 16 bits, you put your value inside [0...1] range
					where   0x0000 = 0.0000
							0xFF00 = 0.5000
							0xFFFF = 1.0000
					with this value, you can now move this value inside any space !
					if you want to put this value inside :
					8 bits space  =   <value> * 0xFF
					12 bits space =   <value> * 0xFFF
					16 bits space =   <value> * 0xFFFF
					32 bits space =   <value> * 0xFFFFFFFF
					"""

					# Map [0...1] range
					print(f"Into Map [0..1]\t:\t", end="")
					x = (pixel[0] / 0xFFFF)
					y = (pixel[1] / 0xFFFF)
					z = (pixel[2] / 0xFFFF)
					print(f"X = {x:<12f}\tY = {y:<12f}\tY = {z:<12f}")

					# Gamma 2.6
					print(f"Gamma 2.6\t:\t", end="")
					x = pow(x, 2.6)
					y = pow(y, 2.6)
					z = pow(z, 2.6)
					print(f"X = {x:<12f}\tY = {y:<12f}\tZ = {z:<12f}")

					# XYZ -> RGB
					print("XYZ to RGB\t:\t", end="")
					red   = (x * 2.7253940305) + (y * -1.0180030062) + (z * -0.4401631952)
					green = (x * -0.7951680258) + (y * 1.6897320548) + (z * 0.0226471906)
					blue  = (x * 0.0412418914) + (y * -0.0876390192) + (z * 1.1009293786)
					print(f"R = {red:<12f}\tG = {green:<12f}\tB = {blue:<12f}")

					# to avoid crash on pow
					if red   < 0: red   = 0
					if green < 0: green = 0
					if blue  < 0: blue  = 0

					# Gamma 1/2.6
					print("Gamma 1/2.6\t:\t", end="")
					red   = pow(red,   1/2.6)
					green = pow(green, 1/2.6)
					blue  = pow(blue,  1/2.6)
					print(f"R = {red:<12f}\tG = {green:<12f}\tB = {blue:<12f}")

					"""
					to multiply with 0xFFFF, you convert your [0...1] to 16 bits.
					if you multiply with 0xFF, you can couvert to 8 bits
					"""
					# Out of Map [0...1] range
					print(f"Out Map [0..1]\t:\t", end="")
					red   = (red   * 0xFFFF)
					green = (green * 0xFFFF)
					blue  = (blue  * 0xFFFF)
					print(f"R = {red:<12f}\tG = {green:<12f}\tB = {blue:<12f}")

					# Rounded
					red   = int(round(red))
					green = int(round(green))
					blue  = int(round(blue))

					"""
					With XYZ reverse matrix, we can have negatif number and number bigger than bitdepth
					for 16 bits, bigger number is 0xFFFF (65535), but sometimes number is bigger than 65535
					"""
					# Check Out-Of-Bound
					if red   > 0xFFFF: red   = 0xFFFF
					if green > 0xFFFF: green = 0xFFFF
					if blue  > 0xFFFF: blue  = 0xFFFF
					if red   < 0: red   = 0
					if green < 0: green = 0
					if blue  < 0: blue  = 0

					print(f"Round+Int (dec)\t:\tR = {red:<12f}\tG = {green:<12f}\tB = {blue:<12f}")
					print(f"Round+Int (hex)\t:\tR = 0x{red:04x}\t\tG = 0x{green:04x}\t\tB = 0x{blue:04x}")

					# push 6 octets (2 octets for each components r,g,b) to bytes format
					# needed for yield
					yield red.to_bytes(2, byteorder='little') \
						+ green.to_bytes(2, byteorder='little') \
						+ blue.to_bytes(2, byteorder='little')



# get informations about input file
image = imread(filename_input)

# shape = (width, height, number_of_components)
print("shape =", image.shape)
# dtype = size_of_component (ex: uint16)
print("dtype =", image.dtype)

print(f"\nwrite to {filename_output}... {image.shape} => {image.dtype}")
imwrite(
	filename_output,
	data=extract_and_convert(filename_input),    # iterate is better for memory
	photometric='rgb',
	shape=image.shape,
	dtype=image.dtype,
	metadata=None
)
