--- /dev/null
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$" />
+ <orderEntry type="jdk" jdkName="Python 3.9 (EU-AAI-00)" jdkType="Python SDK" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
\ No newline at end of file
--- /dev/null
+<component name="InspectionProjectProfileManager">
+ <profile version="1.0">
+ <option name="myName" value="Project Default" />
+ <inspection_tool class="PyStubPackagesAdvertiser" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="ignoredPackages">
+ <list>
+ <option value="pandas-stubs==2.3.3.251219" />
+ </list>
+ </option>
+ </inspection_tool>
+ </profile>
+</component>
\ No newline at end of file
--- /dev/null
+<component name="InspectionProjectProfileManager">
+ <settings>
+ <option name="USE_PROJECT_PROFILE" value="false" />
+ <version value="1.0" />
+ </settings>
+</component>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (EU-AAI-00)" project-jdk-type="Python SDK" />
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/ccd-projecto-61609&57098&70323.iml" filepath="$PROJECT_DIR$/.idea/ccd-projecto-61609&57098&70323.iml" />
+ </modules>
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+import utils\r
+import sys\r
+\r
+_opt_type = sys.argv[1]\r
+_file_path = sys.argv[2]\r
+\r
+_file = utils.get_file(_file_path)\r
+\r
+\r
+if _opt_type == "c":\r
+ _pbm_content = utils.BitMapFile(_file)\r
+ _, _result_option_2 = utils.arithmetic_encode_image(_pbm_content)\r
+ print(_result_option_2)\r
+elif _opt_type == "d":\r
+ _result = utils.arithmetic_decode_file(_file_path)\r
+ print(_result)
\ No newline at end of file
--- /dev/null
+import math
+import sys
+
+def calcular_entropia(bitstream):
+ n = len(bitstream)
+ if n == 0: return 0
+
+ p0 = bitstream.count('0') / n
+ p1 = bitstream.count('1') / n
+
+ entropia = 0
+ for p in [p0, p1]:
+ if p > 0:
+ entropia -= p * math.log2(p)
+ return entropia
+
+def calcular_entropia_condicional(bitstream):
+ transicoes = {'00': 0, '01': 0, '10': 0, '11': 0}
+ contagem_base = {'0': 0, '1': 0}
+
+ for i in range(len(bitstream) - 1):
+ par = bitstream[i:i+2]
+ transicoes[par] += 1
+ contagem_base[bitstream[i]] += 1
+
+ h_condicional = 0
+ for base in ['0', '1']:
+ p_base = contagem_base[base] / (len(bitstream) - 1)
+ if p_base > 0:
+ h_local = 0
+ for prox in ['0', '1']:
+ p_transicao = transicoes[base + prox] / contagem_base[base]
+ if p_transicao > 0:
+ h_local -= p_transicao * math.log2(p_transicao)
+ h_condicional += p_base * h_local
+
+ return h_condicional
+
+def clean_image_data(image_data):
+
+ clean_text = ""
+
+ for line in image_data:
+ line = line.strip()
+ line = line.replace(" ", "")
+ if not line or line.startswith('#'):
+ continue
+ clean_text += line
+
+ return clean_text
+
+with open(sys.argv[1], "r") as image:
+ lines = image.readlines()
+ image_data = clean_image_data(lines[2:])
+
+ h_x = calcular_entropia(image_data)
+ h_condicional = calcular_entropia_condicional(image_data)
+
+ print(f"Entropia: {h_x:.4f} bits/pixel")
+ print(f"Entropia Condicional: {h_condicional:.4f} bits/pixel")
\ No newline at end of file
--- /dev/null
+import utils\r
+import sys\r
+\r
+_opt_type = sys.argv[1]\r
+_file_path = sys.argv[2]\r
+\r
+\r
+if _opt_type == "c":\r
+ _file = utils.get_file(_file_path)\r
+ _pbm_content = utils.BitMapFile(_file)\r
+ _result = utils.huffman_encode_image(_pbm_content)\r
+ with open(sys.argv[2]+".bin", "wb") as file:\r
+ file.write(_result)\r
+ print(_result)\r
+elif _opt_type == "d":\r
+ _result = utils.huffman_decode_file(_file_path)\r
+ with open(sys.argv[2].strip('.bin'), "w") as file:\r
+ file.write(_result)
\ No newline at end of file
--- /dev/null
+import sys
+import struct
+
+########################################################################################
+# python lz_78.py <d/c> <caminho_para_o_ficheiro_original>
+#
+# c: comprimir
+# d: descomprimir
+#
+# Os ficheiros gerados ficarão na mesma pasta que o script
+########################################################################################
+
+def lz78_compression(image):
+ dictionary = {
+ 0: ""
+ }
+
+ output = []
+
+ symbol = ""
+
+ for i in image:
+ if (symbol + i) in dictionary.values():
+ symbol += i
+ else:
+ if symbol == "":
+ output.append([0, i])
+ dictionary[len(dictionary)] = i
+ else:
+ output.append([list(dictionary.keys())[list(dictionary.values()).index(symbol)], i])
+ dictionary[len(dictionary)] = symbol + i
+ symbol = ""
+
+ if symbol != "":
+ idx = list(dictionary.keys())[list(dictionary.values()).index(symbol)]
+ output.append([idx, ""])
+
+ return output, dictionary
+
+def lz78_decompression(compressed_image):
+ dictionary = {
+ 0: ""
+ }
+
+ output = ""
+
+ for i in compressed_image:
+ output += dictionary.get(i[0]) + i[1]
+ dictionary[len(dictionary)] = dictionary.get(i[0]) + i[1]
+
+ return output
+
+def clean_image_data(image_data):
+
+ clean_text = ""
+
+ for line in image_data:
+ line = line.strip()
+ line = line.replace(" ", "")
+ if not line or line.startswith('#'):
+ continue
+ clean_text += line
+
+ return clean_text
+
+def chunkstring(string, length):
+ return [string[i : length+i] for i in range(0, len(string), length)]
+
+#Comprimir
+if sys.argv[1] == "c":
+
+ with open(sys.argv[2], "r") as image:
+ lines = image.readlines()
+ image_size = lines[1]
+
+ if lines[0].strip() != 'P1':
+ raise ValueError("This is not a pbm file")
+
+ clean_text = clean_image_data(lines[2:]) #Limpa os espaços em branco para comprimir melhor
+
+ output, dic = lz78_compression(clean_text)
+
+ print(f"Output: {output}\n")
+ print(f"Dictionary: {dic}")
+
+ #Compressão em texto (human-readable)
+ with open(sys.argv[2] + "_compressed", "w") as save_file:
+ save_file.write(f"{output} \n{image_size}")
+
+ #Compressão em binário (compressão "a sério")
+ with open(sys.argv[2] + ".bin", "wb") as save_file:
+ largura = int(image_size.split()[0])
+ altura = int(image_size.split()[1])
+ save_file.write(struct.pack('II', largura, altura))
+
+ for indice, simbolo in output:
+ simbolo_byte = ord(simbolo) if simbolo != "" else 0
+ save_file.write(struct.pack('HB', indice, simbolo_byte))
+#Descomprimir
+elif sys.argv[1] == "d":
+
+ #Descomprimir do ficheiro de texto
+ with open(sys.argv[2] + "_compressed", "r") as image:
+ lines = image.readlines()
+
+ compressed_image = eval(lines[0])
+ image_size = lines[1]
+ length = int(lines[1].split(" ")[0])
+
+ output = lz78_decompression(compressed_image)
+
+ chuncked_output = chunkstring(output, length)
+
+ with open(sys.argv[2] + "_decompressed", "w") as save_file:
+ save_file.write(f"P1\n{image_size}")
+ for line in chuncked_output:
+ save_file.write(f"{line}\n")
+
+ #Descomprimir do binário
+ with open(sys.argv[2] + ".bin", "rb") as image:
+ header = image.read(8)
+ largura, altura = struct.unpack('II', header)
+
+ compressed_image_bin = []
+ while True:
+ chunk = image.read(3)
+ if not chunk:
+ break
+ indice, simbolo_byte = struct.unpack('HB', chunk)
+ simbolo = chr(simbolo_byte) if simbolo_byte != 0 else ""
+ compressed_image_bin.append([indice, simbolo])
+
+ output = lz78_decompression(compressed_image_bin)
+
+ chuncked_output = chunkstring(output, largura)
+
+ with open(sys.argv[2]+ "_bin_decompressed", "w") as save_file:
+ save_file.write(f"P1\n{largura} {altura}\n")
+ for line in chuncked_output:
+ save_file.write(f"{line}\n")
--- /dev/null
+import sys
+import struct
+
+########################################################################################
+# Apenas comprimir
+#
+# python lz_78_2x2.py <caminho_para_o_ficheiro_original>
+#
+#
+# Os ficheiros gerados ficarão na mesma pasta que o script
+########################################################################################
+
+def lz78_compression(image):
+ dictionary = {0: ""}
+ output = []
+ symbol = ""
+
+ for i in image:
+ if (symbol + i) in dictionary.values():
+ symbol += i
+ else:
+ if symbol == "":
+ output.append([0, i])
+ dictionary[len(dictionary)] = i
+ else:
+ idx = list(dictionary.keys())[list(dictionary.values()).index(symbol)]
+ output.append([idx, i])
+ dictionary[len(dictionary)] = symbol + i
+ symbol = ""
+
+ if symbol != "":
+ idx = list(dictionary.keys())[list(dictionary.values()).index(symbol)]
+ output.append([idx, ""])
+
+ return output, dictionary
+
+def clean_image_data(image_data):
+ clean_text = ""
+ for line in image_data:
+ line = line.strip().replace(" ", "")
+ if not line or line.startswith('#'):
+ continue
+ clean_text += line
+ return clean_text
+
+def chunkstring(string, length):
+ return [string[i : length+i] for i in range(0, len(string), length)]
+
+def get_blocks(bitstream, largura, altura):
+ matriz = [bitstream[i*largura:(i+1)*largura] for i in range(altura)]
+ # Padding
+ if largura % 2 != 0:
+ matriz = [linha + '0' for linha in matriz]
+ largura += 1
+ if altura % 2 != 0:
+ matriz.append('0' * largura)
+ altura += 1
+
+ blocos = []
+ for r in range(0, altura, 2):
+ for c in range(0, largura, 2):
+ bloco = matriz[r][c] + matriz[r][c+1] + matriz[r+1][c] + matriz[r+1][c+1]
+ blocos.append(bloco)
+ return blocos, largura, altura
+
+
+with open(sys.argv[1], "r") as image:
+ lines = image.readlines()
+ image_size = lines[1].split()
+
+ if lines[0].strip() != 'P1':
+ raise ValueError("This is not a pbm file")
+
+ largura_orig = int(image_size[0])
+ altura_orig = int(image_size[1])
+
+ clean_text = clean_image_data(lines[2:])
+
+ image_blocks, largura_pad, altura_pad = get_blocks(clean_text, largura_orig, altura_orig)
+
+ output, dic = lz78_compression(image_blocks)
+
+ with open(sys.argv[1] + "_compressed", "w") as save_file:
+ save_file.write(f"{output}\n{largura_orig} {altura_orig} {largura_pad} {altura_pad}")
+
+ with open(sys.argv[1] + ".bin", "wb") as save_file:
+ save_file.write(struct.pack('IIII', largura_orig, altura_orig, largura_pad, altura_pad))
+ for indice, simbolo in output:
+ simbolo_val = int(simbolo, 2) if simbolo != "" else 16
+ save_file.write(struct.pack('HB', indice, simbolo_val))
\ No newline at end of file
--- /dev/null
+import sys
+import struct
+
+########################################################################################
+# Apenas comprimir
+#
+# python lz_78_rle.py <caminho_para_o_ficheiro_original>
+#
+#
+# Os ficheiros gerados ficarão na mesma pasta que o script
+########################################################################################
+
+def rle_transform(bitstream):
+ sequencia = []
+ pixel_atual = '0' # Começa sempre com o pixel 0 conforme enunciado
+ contador = 0
+
+ for bit in bitstream:
+ if bit == pixel_atual:
+ if contador == 255: # Limite de 8 bits
+ sequencia.append(255)
+ sequencia.append(0) # Troço de 0 do outro pixel para continuar no mesmo [cite: 41]
+ contador = 1
+ else:
+ contador += 1
+ else:
+ sequencia.append(contador)
+ pixel_atual = bit
+ contador = 1
+ sequencia.append(contador)
+ return sequencia
+
+def lz78_compression(image_data):
+ dictionary = {0: []}
+ output = []
+ symbol = []
+
+ for i in image_data:
+ temp = symbol + [i]
+ dict_values = list(dictionary.values())
+ if temp in dict_values:
+ symbol = temp
+ else:
+ if not symbol:
+ output.append([0, i])
+ dictionary[len(dictionary)] = [i]
+ else:
+ idx = dict_values.index(symbol)
+ output.append([idx, i])
+ dictionary[len(dictionary)] = symbol + [i]
+ symbol = []
+
+ if symbol:
+ idx = list(dictionary.values()).index(symbol)
+ output.append([idx, -1])
+ return output
+
+def clean_image_data(image_data):
+
+ clean_text = ""
+
+ for line in image_data:
+ line = line.strip()
+ line = line.replace(" ", "")
+ if not line or line.startswith('#'):
+ continue
+ clean_text += line
+
+ return clean_text
+
+def chunkstring(string, length):
+ return [string[i : length+i] for i in range(0, len(string), length)]
+
+
+with open(sys.argv[1], "r") as image:
+ lines = image.readlines()
+ image_size = lines[1].split()
+
+ if lines[0].strip() != 'P1':
+ raise ValueError("This is not a pbm file")
+
+ clean_text = clean_image_data(lines[2:]) #Limpa os espaços em branco para comprimir melhor
+
+ clean_text = rle_transform(clean_text)
+
+ output = lz78_compression(clean_text)
+
+ print(f"Output: {output}\n")
+
+ #Compressão em texto (human-readable)
+ with open(sys.argv[1] + "_compressed", "w") as save_file:
+ save_file.write(f"{output} \n{image_size[0]} {image_size[1]}")
+
+ #Compressão em binário (compressão "a sério")
+ with open(sys.argv[1] + ".bin", "wb") as save_file:
+ largura = int(image_size[0])
+ altura = int(image_size[1])
+ save_file.write(struct.pack('II', largura, altura))
+
+ for indice, val in output:
+ simbolo_byte = val if val != -1 else 255
+ save_file.write(struct.pack('HB', indice, simbolo_byte))
--- /dev/null
+import sys
+import struct
+
+########################################################################################
+# python lz_78_xor.py <d/c> <caminho_para_o_ficheiro_original>
+#
+# c: comprimir
+# d: descomprimir
+#
+# Os ficheiros gerados ficarão na mesma pasta que o script
+########################################################################################
+
+def xor(bitstream, largura, altura):
+ matriz = []
+ for i in range(altura):
+ linha = [int(b) for b in bitstream[i * largura : (i + 1) * largura]]
+ matriz.append(linha)
+
+ nova_imagem = ""
+ # A primeira linha mantém-se igual (não tem linha anterior)
+ nova_imagem += "".join(map(str, matriz[0]))
+
+ for i in range(1, altura):
+ nova_linha = []
+ for j in range(largura):
+ res = matriz[i][j] ^ matriz[i-1][j]
+ nova_linha.append(str(res))
+ nova_imagem += "".join(nova_linha)
+
+ return nova_imagem
+
+def descodificar_xor(bitstream_transformado, largura, altura):
+ matriz_temp = []
+ for i in range(altura):
+ linha = [int(b) for b in bitstream_transformado[i * largura : (i + 1) * largura]]
+ matriz_temp.append(linha)
+
+ matriz_original = []
+
+ matriz_original.append(matriz_temp[0])
+
+ for i in range(1, altura):
+ linha_recuperada = []
+ for j in range(largura):
+ pixel_original = matriz_temp[i][j] ^ matriz_original[i-1][j]
+ linha_recuperada.append(pixel_original)
+ matriz_original.append(linha_recuperada)
+
+ bitstream_final = ""
+ for linha in matriz_original:
+ bitstream_final += "".join(map(str, linha))
+
+ return bitstream_final
+
+def lz78_compression(image):
+ dictionary = {
+ 0: ""
+ }
+
+ output = []
+
+ symbol = ""
+
+ for i in image:
+ if (symbol + i) in dictionary.values():
+ symbol += i
+ else:
+ if symbol == "":
+ output.append([0, i])
+ dictionary[len(dictionary)] = i
+ else:
+ output.append([list(dictionary.keys())[list(dictionary.values()).index(symbol)], i])
+ dictionary[len(dictionary)] = symbol + i
+ symbol = ""
+
+ if symbol != "":
+ idx = list(dictionary.keys())[list(dictionary.values()).index(symbol)]
+ output.append([idx, ""])
+
+ return output, dictionary
+
+def lz78_decompression(compressed_image):
+ dictionary = {
+ 0: ""
+ }
+
+ output = ""
+
+ for i in compressed_image:
+ output += dictionary.get(i[0]) + i[1]
+ dictionary[len(dictionary)] = dictionary.get(i[0]) + i[1]
+
+ return output
+
+def clean_image_data(image_data):
+
+ clean_text = ""
+
+ for line in image_data:
+ line = line.strip()
+ line = line.replace(" ", "")
+ if not line or line.startswith('#'):
+ continue
+ clean_text += line
+
+ return clean_text
+
+def chunkstring(string, length):
+ return [string[i : length+i] for i in range(0, len(string), length)]
+
+#Comprimir
+if sys.argv[1] == "c":
+
+ with open(sys.argv[2], "r") as image:
+ lines = image.readlines()
+ image_size = lines[1].split()
+
+ if lines[0].strip() != 'P1':
+ raise ValueError("This is not a pbm file")
+
+ clean_text = clean_image_data(lines[2:]) #Limpa os espaços em branco para comprimir melhor
+
+ clean_text = xor(clean_text, int(image_size[0]), int(image_size[1]))
+
+ output, dic = lz78_compression(clean_text)
+
+ print(f"Output: {output}\n")
+ print(f"Dictionary: {dic}")
+
+ #Compressão em texto (human-readable)
+ with open(sys.argv[2] + "_compressed", "w") as save_file:
+ save_file.write(f"{output} \n{image_size[0]} {image_size[1]}")
+
+ #Compressão em binário (compressão "a sério")
+ with open(sys.argv[2] + ".bin", "wb") as save_file:
+ largura = int(image_size[0])
+ altura = int(image_size[1])
+ save_file.write(struct.pack('II', largura, altura))
+
+ for indice, simbolo in output:
+ simbolo_byte = ord(simbolo) if simbolo != "" else 0
+ save_file.write(struct.pack('HB', indice, simbolo_byte))
+#Descomprimir
+elif sys.argv[1] == "d":
+
+ #Descomprimir do ficheiro de texto
+ with open(sys.argv[2] + "_compressed", "r") as image:
+ lines = image.readlines()
+
+ compressed_image = eval(lines[0])
+ image_size = lines[1]
+ largura = int(lines[1].split(" ")[0])
+ altura = int(lines[1].split(" ")[1])
+
+ output = lz78_decompression(compressed_image)
+
+ output = descodificar_xor(output, largura, altura)
+
+ chuncked_output = chunkstring(output, largura)
+
+ with open(sys.argv[2] + "_decompressed", "w") as save_file:
+ save_file.write(f"P1\n{image_size}\n")
+ for line in chuncked_output:
+ save_file.write(f"{line}\n")
+
+ #Descomprimir do binário
+ with open(sys.argv[2] + ".bin", "rb") as image:
+ header = image.read(8)
+ largura, altura = struct.unpack('II', header)
+
+ compressed_image_bin = []
+ while True:
+ chunk = image.read(3)
+ if not chunk:
+ break
+ indice, simbolo_byte = struct.unpack('HB', chunk)
+ simbolo = chr(simbolo_byte) if simbolo_byte != 0 else ""
+ compressed_image_bin.append([indice, simbolo])
+
+ output = lz78_decompression(compressed_image_bin)
+
+ output = descodificar_xor(output, largura, altura)
+
+ chuncked_output = chunkstring(output, largura)
+
+ with open(sys.argv[2]+ "_bin_decompressed", "w") as save_file:
+ save_file.write(f"P1\n{largura} {altura}\n")
+ for line in chuncked_output:
+ save_file.write(f"{line}\n")
--- /dev/null
+import utils\r
+\r
+if __name__ == '__main__':\r
+ _original = utils.get_file('pixel_character.pbm')\r
+ #_original = utils.get_file('tetris_example.pbm')\r
+ _pbm_content = utils.BitMapFile(_original)\r
+\r
+ while True:\r
+ print("\n1) print file info")\r
+ print("2) Preview arithmetic codification info ")\r
+ print("3) Encode with arithmetic style ")\r
+ print("4) Decode with arithmetic style ")\r
+ print("5) Apply XOR to image before compression \n")\r
+ _user_input = input("your option --> ")\r
+\r
+ if _user_input == "1":\r
+ print("\n*** PORTABLE BITMAP FILE INFO ***")\r
+ print(f"magic number:{_pbm_content.magic_number},\nimage width:{_pbm_content.image_width},image height:{_pbm_content.image_height}")\r
+ print(f"Zeros ---> {_pbm_content.zeros}")\r
+ print(f"Uns ---> {_pbm_content.ones}")\r
+ print("***** \t ******** \t *****\n")\r
+ input("<back to menu>")\r
+ if _user_input == "2":\r
+ utils.arithmetic_cod_preview(_pbm_content)\r
+ if _user_input == "3":\r
+ _result_option_1, _result_option_2 = utils.arithmetic_encode_image(_pbm_content)\r
+ with open('arithmetic_output_opt1.txt', 'w') as f:\r
+ f.write(_result_option_1)\r
+ with open('arithmetic_output_opt2.txt', 'w') as f:\r
+ f.write(_result_option_2)\r
+ if _user_input == "4":\r
+ _result = utils.arithmetic_decode_file('arithmetic_output_opt2.txt')\r
+ with open('arithmetic_decode_output.pbm', 'w') as f:\r
+ f.write(_result)\r
+ if _user_input == "5":\r
+ _new_pbm = _pbm_content\r
+ _xor_on_image = utils.xor(_new_pbm.image_array,int(_new_pbm.image_width),int(_new_pbm.image_height))\r
+ _new_pbm.image_arra = _xor_on_image\r
+ _, _result = utils.arithmetic_encode_image(_new_pbm)\r
+ print(_result)\r
+ with open('arithmetic_output_wxor.txt', 'w') as f:\r
+ f.write(_result)
\ No newline at end of file
--- /dev/null
+P1
+48 48
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 1 1 1 1 1 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
--- /dev/null
+Universidade de Évora\r
+Projeto da disciplina de Compressão de Dados - 2025/2026\r
+Compressão de Imagens Binárias\r
+\r
+\r
+LZ78 - André Moleirinho (61609)\r
+- lz_78.py\r
+- lz_78_xor.py\r
+- lz_78_2x2.py\r
+- lz_78_rle.py\r
+\r
+Huffman - Fábio Macarrão (57098)\r
+- huffman.py\r
+- utils.py\r
+\r
+Aritmética - Vitor Costa (70323)\r
+- arithmetic.py\r
+- utils.py\r
+\r
+\r
+Executar os scripts em ficheiros pbm:\r
+ > python <ScriptName> <c/d> <PathToFile>\r
+\r
+\r
--- /dev/null
+import os
+import sys
+
+def calcular_taxa_compressao(caminho_original, caminho_comprimido, total_pixeis):
+
+ tamanho_orig = os.path.getsize(caminho_original)
+ tamanho_comp = os.path.getsize(caminho_comprimido)
+
+ poupanca = (1 - (tamanho_comp / tamanho_orig)) * 100
+
+ bpp = (tamanho_comp * 8) / total_pixeis
+
+ return poupanca, bpp, tamanho_orig, tamanho_comp
+
+def clean_image_data(image_data):
+
+ clean_text = ""
+
+ for line in image_data:
+ line = line.strip()
+ line = line.replace(" ", "")
+ if not line or line.startswith('#'):
+ continue
+ clean_text += line
+
+ return clean_text
+
+with open(sys.argv[1], "r") as ficheiro:
+ lines = ficheiro.readlines()
+ image_data = clean_image_data(lines[2:])
+
+ largura, altura = lines[1].split(" ")
+
+ total_pixeis = int(largura) * int(altura)
+ p, b, t_o, t_c = calcular_taxa_compressao(sys.argv[1], sys.argv[1] + "_compressed", len(image_data))
+
+print(f"Tamanho Original: {t_o} bytes")
+print(f"Tamanho Comprimido: {t_c} bytes")
+print(f"Redução: {p:.2f}%")
+print(f"Rácio: {b:.4f} bits/pixel")
\ No newline at end of file
--- /dev/null
+P1\r
+4 5\r
+0 0 0 0\r
+0 0 1 0\r
+0 1 1 0\r
+0 1 0 0\r
+0 0 0 0\r
--- /dev/null
+import os\r
+import math\r
+from collections import Counter\r
+import heapq\r
+import numpy as np\r
+import struct\r
+\r
+\r
+class BitMapFile:\r
+ def __init__(self, text):\r
+ self._text = text.split()\r
+ self.magic_number = self._text[0]\r
+ self.image_width = self._text[1]\r
+ self.image_height = self._text[2]\r
+ self.image_array = self._text[3:]\r
+ self.zeros = 0\r
+ self.ones = 0\r
+ self.size = int(self.image_width) * int(self.image_height)\r
+\r
+ for _ in range(len(self.image_array)):\r
+ if self.image_array[_] == "0":\r
+ self.zeros += 1\r
+ elif self.image_array[_] == "1":\r
+ self.ones += 1\r
+ else:\r
+ print("ignore")\r
+\r
+\r
+"""\r
+To read files\r
+"""\r
+def get_file(file_path):\r
+ with open(file_path, 'r', encoding='utf-8') as file:\r
+ _file_content = file.read()\r
+ return _file_content\r
+\r
+"""\r
+To evaluate comprimento médio do código - l(c)\r
+ l(all_pixels) = ceil(log2P(all_pixels))+1\r
+"""\r
+def arithmetic_cod_preview(_pbm):\r
+ print("\n*** Arithmetic Preview ***")\r
+ pixels = [int(pixel) for pixel in _pbm.image_array]\r
+ _P0 = _pbm.zeros/_pbm.size\r
+ _P1 = _pbm.ones/_pbm.size\r
+ _PT01_int = 1\r
+\r
+ for pixel in pixels:\r
+ if pixel == 0:\r
+ _PT01_int *= _P0\r
+ elif pixel == 1:\r
+ _PT01_int *= _P1\r
+\r
+ _arithmetic_encode_lenght = np.ceil(-1*np.log2(_PT01_int)) + 1\r
+ print(f"L(image) --> {_arithmetic_encode_lenght} bits")\r
+ _entropy = calcular_entropia(_pbm.image_array)\r
+ print(f"entropy --> {_entropy} bits")\r
+ _datastream = clean_image_data(_pbm.image_array)\r
+ _entropia_conditional = calcular_entropia_condicional(_datastream)\r
+ print(f"entropy condicional --> {_entropia_conditional} bits")\r
+ return int(_arithmetic_encode_lenght)\r
+\r
+"""\r
+Arithmetic Encoding approach\r
+\r
+it uses static probabilities provided on output file\r
+\r
+c(x)=|interval_low_val +(interval_high_val).F(x)_low\r
+ =|interval_low_val +(interval_high_val).F(x)_high\r
+_to_encode_val = avg(C(all)_low,C(all)_high)\r
+_output = bin(_to_encode_val))\r
+"""\r
+def arithmetic_encode_image(_pbm):\r
+ pixels = [int(pixel) for pixel in _pbm.image_array]\r
+ _P0 = np.float64(_pbm.zeros/_pbm.size)\r
+ _P1 = np.float64(_pbm.ones/_pbm.size)\r
+ _interval = [0,1]\r
+ # probabilidades acomuladas orderdanas por ordem crescente\r
+ _Fx = {1: (0,_P1),\r
+ 0: (_P1,1)}\r
+ _output_opt_1 = f"{_pbm.zeros} {_pbm.ones} {_pbm.image_width} {_pbm.image_height} "\r
+\r
+ for pixel in pixels:\r
+ if pixel == 0:\r
+ _low_val = np.float64(_interval[0] + (_interval[1]-_interval[0]) * _Fx.get(0)[0])\r
+ _high_val = np.float64(_interval[0] + (_interval[1]-_interval[0])*_Fx.get(0)[1])\r
+ if pixel == 1:\r
+ _low_val = np.float64(_interval[0] + (_interval[1] - _interval[0]) * _Fx.get(1)[0])\r
+ _high_val = np.float64(_interval[0] + (_interval[1] - _interval[0]) * _Fx.get(1)[1])\r
+ _interval[0] = _low_val\r
+ _interval[1] = _high_val\r
+\r
+ _to_encode_value = np.float64((_interval[0]+_interval[1])/2)\r
+ print(f"to encode --> {_to_encode_value}")\r
+ _output_opt_2 = _output_opt_1 + f"{_to_encode_value}"\r
+\r
+ _size = arithmetic_cod_preview(_pbm)\r
+ _res = _to_encode_value\r
+ for _ in range(_size):\r
+ _res *=2\r
+ if _res < 1:\r
+ _output_opt_1 += "0"\r
+ else:\r
+ _output_opt_1 += "1"\r
+ _res = _res-1\r
+\r
+\r
+ return _output_opt_1,_output_opt_2\r
+\r
+"""\r
+To decode encoded pbm files with arithmetic_encode_image()\r
+ amount_of_zeros amount_of_ones image_width image_height encoded_image\r
+"""\r
+def arithmetic_decode_file(_txt):\r
+ _encoded_pbm_file = get_file(_txt).split()\r
+\r
+ amount_of_zeros = int(_encoded_pbm_file[0])\r
+ amount_of_ones = int(_encoded_pbm_file[1])\r
+ image_width = int(_encoded_pbm_file[2])\r
+ image_height = int(_encoded_pbm_file[3])\r
+ encoded_image = np.float64(_encoded_pbm_file[4])\r
+\r
+ _output = f"P1\n{image_width} {image_height}\n"\r
+ _size = int(image_width) * int(image_height)\r
+ _P0 = np.float64(amount_of_zeros/_size)\r
+ _P1 = np.float64(amount_of_ones/_size)\r
+ _Fx = {1:(0,_P1),\r
+ 0:(_P1,1)}\r
+\r
+ _constructed_image = ""\r
+ for h in range(image_height):\r
+ for w in range(image_width):\r
+ if _P1 < encoded_image < 1:\r
+ _constructed_image += "0 "\r
+ encoded_image = (encoded_image-_P1)/_P0\r
+ elif 0 < encoded_image < _P1:\r
+ _constructed_image += "1 "\r
+ encoded_image = (encoded_image - 0) / _P1\r
+\r
+ if w == image_width-1:\r
+ _constructed_image += "\n"\r
+\r
+ _output += _constructed_image\r
+ return _output\r
+\r
+\r
+def calcular_entropia(bitstream):\r
+ n = len(bitstream)\r
+ if n == 0: return 0\r
+\r
+ p0 = bitstream.count('0') / n\r
+ p1 = bitstream.count('1') / n\r
+\r
+ entropia = 0\r
+ for p in [p0, p1]:\r
+ if p > 0:\r
+ entropia -= p * math.log2(p)\r
+ return entropia\r
+\r
+\r
+def calcular_entropia_condicional(bitstream):\r
+ transicoes = {'00': 0, '01': 0, '10': 0, '11': 0}\r
+ contagem_base = {'0': 0, '1': 0}\r
+\r
+ for i in range(len(bitstream) - 1):\r
+ par = bitstream[i:i + 2]\r
+ transicoes[par] += 1\r
+ contagem_base[bitstream[i]] += 1\r
+\r
+ h_condicional = 0\r
+ for base in ['0', '1']:\r
+ p_base = contagem_base[base] / (len(bitstream) - 1)\r
+ if p_base > 0:\r
+ h_local = 0\r
+ for prox in ['0', '1']:\r
+ p_transicao = transicoes[base + prox] / contagem_base[base]\r
+ if p_transicao > 0:\r
+ h_local -= p_transicao * math.log2(p_transicao)\r
+ h_condicional += p_base * h_local\r
+\r
+ return h_condicional\r
+\r
+\r
+def clean_image_data(image_data):\r
+ clean_text = ""\r
+\r
+ for line in image_data:\r
+ line = line.strip()\r
+ line = line.replace(" ", "")\r
+ if not line or line.startswith('#'):\r
+ continue\r
+ clean_text += line\r
+\r
+ return clean_text\r
+\r
+\r
+def calcular_taxa_compressao(caminho_original, caminho_comprimido, total_pixeis):\r
+ tamanho_orig = os.path.getsize(caminho_original)\r
+ tamanho_comp = os.path.getsize(caminho_comprimido)\r
+\r
+ poupanca = (1 - (tamanho_comp / tamanho_orig)) * 100\r
+\r
+ bpp = (tamanho_comp * 8) / total_pixeis\r
+\r
+ return poupanca, bpp, tamanho_orig, tamanho_comp\r
+\r
+\r
+def xor(bitstream, largura, altura):\r
+ matriz = []\r
+ for i in range(altura):\r
+ linha = [int(b) for b in bitstream[i * largura: (i + 1) * largura]]\r
+ matriz.append(linha)\r
+\r
+ nova_imagem = ""\r
+ # A primeira linha mantém-se igual (não tem linha anterior)\r
+ nova_imagem += "".join(map(str, matriz[0]))\r
+\r
+ for i in range(1, altura):\r
+ nova_linha = []\r
+ for j in range(largura):\r
+ res = matriz[i][j] ^ matriz[i - 1][j]\r
+ nova_linha.append(str(res))\r
+ nova_imagem += "".join(nova_linha)\r
+\r
+ return nova_imagem\r
+\r
+\r
+def descodificar_xor(bitstream_transformado, largura, altura):\r
+ matriz_temp = []\r
+ for i in range(altura):\r
+ linha = [int(b) for b in bitstream_transformado[i * largura: (i + 1) * largura]]\r
+ matriz_temp.append(linha)\r
+\r
+ matriz_original = []\r
+\r
+ matriz_original.append(matriz_temp[0])\r
+\r
+ for i in range(1, altura):\r
+ linha_recuperada = []\r
+ for j in range(largura):\r
+ pixel_original = matriz_temp[i][j] ^ matriz_original[i - 1][j]\r
+ linha_recuperada.append(pixel_original)\r
+ matriz_original.append(linha_recuperada)\r
+\r
+ bitstream_final = ""\r
+ for linha in matriz_original:\r
+ bitstream_final += "".join(map(str, linha))\r
+\r
+ return bitstream_final\r
+\r
+\r
+# -------------------------------------------------------------------------\r
+# HUFFMAN IMPLEMENTATION\r
+# -------------------------------------------------------------------------\r
+\r
+class HuffmanNode:\r
+ def __init__(self, char, freq):\r
+ self.char = char # 0 ou 1 que está no pixel\r
+ self.freq = freq # Quantas vezes aparece\r
+ self.left = None # Filho à esquerda\r
+ self.right = None # Filho à direita\r
+\r
+ # Nó com menor frequencia\r
+ def __lt__(self, other):\r
+ return self.freq < other.freq\r
+\r
+\r
+# Constroi a árvore\r
+def build_huffman_tree(pixels):\r
+ frequency = Counter(pixels)\r
+ heap = [HuffmanNode(char, freq) for char, freq in frequency.items()]\r
+ heapq.heapify(heap)\r
+\r
+ # Cria a árvore de baixo para cima / frequencia menor para maior\r
+ while len(heap) > 1:\r
+ node1 = heapq.heappop(heap)\r
+ node2 = heapq.heappop(heap)\r
+ merged = HuffmanNode(None, node1.freq + node2.freq)\r
+ merged.left = node1\r
+ merged.right = node2\r
+ heapq.heappush(heap, merged)\r
+\r
+ return heap[0] if heap else None\r
+\r
+\r
+# Cria os códigos (0 para a esquerda, 1 para a direira)\r
+def make_codes(node, current_code="", codes=None):\r
+ if codes is None:\r
+ codes = {}\r
+ if node is None:\r
+ return codes\r
+ if node.char is not None:\r
+ codes[node.char] = current_code\r
+ return codes\r
+\r
+ make_codes(node.left, current_code + "0", codes)\r
+ make_codes(node.right, current_code + "1", codes)\r
+ return codes\r
+\r
+\r
+# Faz encoding do ficheiro\r
+def huffman_encode_image(_pbm):\r
+ print("\n*** Huffman Encoding ***")\r
+ pixels = _pbm.image_array\r
+\r
+ # Erro caso não encontre a imagem\r
+ if not pixels:\r
+ print("Empty image.")\r
+ return None\r
+\r
+ # Constroi a árvore e os códigos\r
+ root = build_huffman_tree(pixels)\r
+ codes = make_codes(root)\r
+\r
+ print(f"Codes: {codes}")\r
+\r
+ # Faz encode dos pixeis\r
+ encoded_str = "".join([codes[p] for p in pixels])\r
+\r
+ # Garante que o total é multiplo de 8\r
+ extra_padding = 8 - len(encoded_str) % 8\r
+ encoded_str = encoded_str + "0" * extra_padding\r
+\r
+ # Reduz o tamanho convertendo de byte para bit\r
+ b = bytearray()\r
+ for i in range(0, len(encoded_str), 8):\r
+ byte = encoded_str[i:i + 8]\r
+ b.append(int(byte, 2))\r
+\r
+ # Cabeçalho\r
+ width = int(_pbm.image_width)\r
+ height = int(_pbm.image_height)\r
+ freq0 = _pbm.zeros\r
+ freq1 = _pbm.ones\r
+ header = struct.pack('>IIIIB', width, height, freq0, freq1, extra_padding)\r
+\r
+ return header + b\r
+\r
+\r
+# Faz decode do ficheiro\r
+def huffman_decode_file(filename):\r
+ print(f"\n*** Huffman Decoding from {filename} ***")\r
+\r
+ # Lê o ficheiro\r
+ with open(filename, 'rb') as f:\r
+ file_content = f.read()\r
+\r
+ # Separa o cabeçalho\r
+ header_size = struct.calcsize('>IIIIB')\r
+ width, height, freq0, freq1, extra_padding = struct.unpack('>IIIIB', file_content[:header_size])\r
+ encoded_data = file_content[header_size:]\r
+\r
+ print(f"Header Info -> W:{width}, H:{height}, Zeros:{freq0}, Ones:{freq1}")\r
+\r
+ # Constroi a árvore\r
+ fake_pixels = ['0'] * freq0 + ['1'] * freq1\r
+ root = build_huffman_tree(fake_pixels)\r
+ codes = make_codes(root)\r
+\r
+ # Faz decode dos pixeis\r
+ reverse_codes = {v: k for k, v in codes.items()}\r
+\r
+ # Converte os bytes em string\r
+ bit_string = ""\r
+ for byte in encoded_data:\r
+ bit_string += f"{byte:08b}"\r
+\r
+ # Desencripta\r
+ decoded_pixels = []\r
+ current_code = ""\r
+ for bit in bit_string:\r
+ current_code += bit\r
+ if current_code in reverse_codes:\r
+ decoded_pixels.append(reverse_codes[current_code])\r
+ current_code = ""\r
+\r
+ # Limita o tamanho width * height\r
+ expected_pixels = width * height\r
+ decoded_pixels = decoded_pixels[:expected_pixels]\r
+\r
+ pbm_content = f"P1\n{width} {height}\n"\r
+\r
+ # Formata as linhas para não ficar sequencial\r
+ row_str = ""\r
+ for i, p in enumerate(decoded_pixels):\r
+ row_str += p + " "\r
+ if (i + 1) % width == 0:\r
+ pbm_content += row_str.strip() + "\n"\r
+ row_str = ""\r
+\r
+ print("Decoding finished.")\r
+ return pbm_content\r