-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathgraficos_bitmap.py
1240 lines (1166 loc) · 53.8 KB
/
graficos_bitmap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: iso-8859-15 -*-
# NAPS: The New Age PAW-like System - Herramientas para sistemas PAW-like
#
# Librería para operar con bases de datos gráficas y otros gráficos de mapa de bits
# Copyright (C) 2008, 2018-2024 José Manuel Ferrer Ortiz
#
# *****************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify it *
# * under the terms of the GNU General Public License version 2, as *
# * published by the Free Software Foundation. *
# * *
# * This program is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * General Public License version 2 for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * version 2 along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
# * *
# *****************************************************************************
import os
import sys
from bajo_nivel import *
traza = False # Si queremos traza del funcionamiento del módulo
if traza:
from prn_func import prn
# Paleta de colores por defecto de varios modos gráficos, sin reordenar para textos
colores_por_defecto = {
'VGA': (( 0, 0, 0), (255, 255, 255), (255, 0, 0), ( 0, 255, 0),
( 0, 0, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255),
(255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255),
(255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255)),
}
# Número de colores de cada modo gráfico
colores_por_modo = {
'CGA': 4,
'EGA': 16,
'PCW': 2,
'ST': 16,
'VGA': 16,
}
# Marca de orden de componentes de color con el bit menos significativo en el bit más bajo
marca_amiga = (0xDA, 0xAD, 0xDA, 0xAD)
# Resolución de cada modo gráfico
resolucion_por_modo = {
'CGA': (320, 200),
'EGA': (320, 200),
'PCW': (720, 256),
'ST': (320, 200),
'VGA': (320, 200),
}
# Paletas CGA 1 y 2 con y sin brillo, en el orden necesario
paleta1b = ((0, 0, 0), (85, 255, 255), (255, 85, 255), (255, 255, 255))
paleta2b = ((0, 0, 0), (85, 255, 85), (255, 85, 85), (255, 255, 85))
paleta1s = ((0, 0, 0), ( 0, 170, 170), (170, 0, 170), (170, 170, 170))
paleta2s = ((0, 0, 0), ( 0, 170, 0), (170, 0, 0), (170, 85, 0))
# Paletas de blanco y negro, en el orden necesario
paletaBN = ((255, 255, 255), (0, 0, 0))
paletaNB = ((0, 0, 0), (255, 255, 255))
# Paleta EGA en el orden necesario
paletaEGA = (( 0, 0, 0), ( 0, 0, 170), ( 0, 170, 0), ( 0, 170, 170),
(170, 0, 0), (170, 0, 170), (170, 85, 0), (170, 170, 170),
( 85, 85, 85), ( 85, 85, 255), ( 85, 255, 85), ( 85, 255, 255),
(255, 85, 85), (255, 85, 255), (255, 255, 85), (255, 255, 255))
# Paleta PCW en el orden necesario, negro y verde
paletaPCW = ((0, 0, 0), (0, 255, 0))
# Valores 'hardcodeados' para las imágenes de las aventuras SWAN
imagenesSWAN = {
'mindf': {
'caracterBorde': ( # Los índices de color son sobre la paleta reordenada para textos
0, 0, 0, 0, 0, 0, 0, 0,
0, 2, 2, 2, 2, 2, 2, 0,
0, 2, 0, 0, 0, 0, 2, 0,
0, 2, 0, 2, 2, 0, 2, 0,
0, 2, 0, 2, 2, 0, 2, 0,
0, 2, 0, 0, 0, 0, 2, 0,
0, 2, 2, 2, 2, 2, 2, 0,
0, 0, 0, 0, 0, 0, 0, 0,
),
'imagenPorDefecto': 1,
'imagenPorLocalidad': (3, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 7, 5, 5, 1, 1, 1, 3, 3, 1, 7, 1, 1, 7, 7, 3, 3, 3, 3, 1, 1, 8, 8, 1, 8, 8, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 4, 4, 10, 9, 10, 10, 9, 9, 10, 10, 10, 9, 10, 11, 11, 10, 9, 9, 10, 10, 9, 10, 11, 11, 10, 10, 11, 11, 11, 11, 11, 10, 10, 1, 16, 16, 16, 16, 16, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 14, 13, 12, 4, 1, 6)
}
}
indice_nuevos = -1 # Índice para almacenar la posición de nuevos recursos
le = None # Si el formato de la base de datos gráfica es Little Endian
long_cabecera = None # Longitud de la cabecera de la base de datos
long_cabecera_rec = None # Longitud de la cabecera de recurso
modo_gfx = None # Modo gráfico
pos_recursos = {} # Asociación entre cada posición de recurso, y los números de recurso que la usan
recursos = [] # Gráficos y sonidos de la base de datos gráfica
# Funciones que utilizan el IDE, el editor de bases de datos gráficas, o el intérprete directamente
def cambia_imagen (numRecurso, ancho, alto, imagen, paleta):
global indice_nuevos
if recursos[numRecurso]:
recurso = recursos[numRecurso]
pos_recursos[recurso['desplazamiento']].remove (numRecurso)
limpiarPosicion = recurso['desplazamiento']
else:
limpiarPosicion = None
recurso = {'banderas': set(), 'banderasInt': 0, 'desplazamiento': None, 'posicion': (0, 0)}
# Vemos si ya había algún recurso con la misma imagen
for recursoExistente in recursos:
if not recursoExistente:
continue # Saltamos los recursos inexistentes
if recursoExistente['dimensiones'] == (ancho, alto) and imagen == recursoExistente['imagen']:
recurso['desplazamiento'] = recursoExistente['desplazamiento']
pos_recursos[recurso['desplazamiento']].append (numRecurso)
break
else: # No había ninguno con la misma imagen
recurso['desplazamiento'] = indice_nuevos
pos_recursos[indice_nuevos] = [numRecurso]
indice_nuevos -= 1
# Quitamos la entrada de pos_recursos si ya no queda ninguna imagen con el desplazamiento que tenía la imagen anterior
if limpiarPosicion != None and not len (pos_recursos[limpiarPosicion]):
del pos_recursos[limpiarPosicion]
if modo_gfx == 'CGA':
if paleta == paleta1b:
recurso['banderas'].add ('cgaPaleta1')
recurso['banderasInt'] |= 4 if version > 1 else 128
elif 'cgaPaleta1' in recurso['banderas']:
recurso['banderas'].remove ('cgaPaleta1')
if recurso['banderasInt'] & (4 if version > 1 else 128):
recurso['banderasInt'] -= 4 if version > 1 else 128
recurso['dimensiones'] = (ancho, alto)
recurso['imagen'] = imagen
recurso['paleta'] = paleta
recursos[numRecurso] = recurso
def carga_bd_pics (nombreFichero):
"""Carga a memoria la base de datos gráfica desde el fichero de nombre dado. Devuelve un mensaje de error si falla"""
global fichero
try:
fichero = open (nombreFichero, 'rb')
except IOError as excepcion:
return excepcion.args[1]
extension = nombreFichero[-4:].lower()
if extension not in ('.cga', '.dat', '.ega', '.pcw'):
return 'Extensión %s inválida para bases de datos gráficas de DAAD' % extension
bajo_nivel_cambia_ent (fichero)
for funcion, parametros in ((preparaPlataforma, (extension, fichero)), (cargaRecursos, ())):
try:
msgError = funcion (*parametros)
if msgError:
fichero.close()
return msgError
except Exception as excepcion:
fichero.close()
return excepcion.args[0]
fichero.close()
def carga_fuente (fichero):
"""Carga y devuelve una fuente tipográfica de DAAD junto con su paleta, como índices en la paleta de cada píxel, organizados como en fuente.png"""
bajo_nivel_cambia_ent (fichero)
fichero.seek (0, os.SEEK_END)
longFichero = fichero.tell()
fichero.seek ((128 if 3200 > longFichero > 2048 else 0) + (16 * 8 if longFichero < 3200 else 0))
ancho = 628
alto = 38 + (0 if longFichero < 3200 else 10)
imagen = [0] * ancho * alto # Índices en la paleta de cada píxel en la imagen
for caracter in range (256 - (16 if longFichero < 3200 else 0)):
posPorCol = (caracter % 63) * 10
posPorFila = (caracter // 63) * 10 * ancho
for fila in range (8):
b = carga_int1() # Byte actual
for indiceBit in range (8): # Cada bit del byte actual
imagen[posPorFila + posPorCol + indiceBit] = 0 if b & (2 ** (7 - indiceBit)) else 1
posPorFila += ancho
return imagen, paletaBN
def carga_fuente_zx_8 (fichero):
"""Carga y devuelve una fuente tipográfica de ZX Spectrum de 8x8 junto con su paleta, como índices en la paleta de cada píxel, organizados como en fuente.png"""
posicion = detectaFuente8 (fichero)
if posicion == None:
return [], []
bajo_nivel_cambia_ent (fichero)
fichero.seek (posicion)
ancho = 628
alto = 48
imagen = [0] * ancho * alto # Índices en la paleta de cada píxel en la imagen
for caracter in range (96):
posPorCol = (caracter % 63) * 10
posPorFila = (caracter // 63) * 10 * ancho
for fila in range (8):
b = carga_int1() # Fila actual
for indiceBit in range (8): # Cada bit de la fila actual
imagen[posPorFila + posPorCol + indiceBit] = 0 if b & (2 ** (7 - indiceBit)) else 1
posPorFila += ancho
return imagen, paletaBN
def carga_imagen_pix (fichero):
"""Carga y devuelve una imagen PIX de SWAN junto con su paleta y dimensiones, como índices en la paleta de cada píxel, detectando su modo gráfico"""
fichero.seek (0, os.SEEK_END)
longFichero = fichero.tell()
fichero.seek (0)
if longFichero < 15488: # Como no ocupa lo suficiente para poder ser una imagen de Atari, asumimos que es en blanco y negro
dimensiones = (304, 64)
imagen, paleta = cargaImagenPIXenBN (fichero)
else:
dimensiones = (320, 96)
imagen, paleta = cargaImagenAtari (fichero)
return imagen, paleta, dimensiones
def carga_portada (fichero, pareceST = False):
"""Carga y devuelve una portada de DAAD junto con su paleta, como índices en la paleta de cada píxel, detectando su modo gráfico"""
fichero.seek (0, os.SEEK_END)
longFichero = fichero.tell()
fichero.seek (0)
if longFichero in (16000, 16384):
return cargaPortadaCGA (fichero, longFichero)
if longFichero == 32001:
return cargaPortadaEGA (fichero)
if longFichero == 32049:
return cargaPortadaVGA (fichero)
if (longFichero == 32034 and not pareceST) or (longFichero in (32127, 32128)):
return cargaPortadaAmiga (fichero)
if longFichero in (32034, 32066):
return cargaPortadaAtari (fichero)
return None
def carga_udgs_zx (fichero, posicion, numUDGs):
"""Carga y devuelve el número dado de UDGs de ZX Spectrum de 8x8 junto con su paleta, como índices en la paleta de cada píxel"""
bajo_nivel_cambia_ent (fichero)
fichero.seek (posicion)
ancho = numUDGs * 8
alto = 8
imagen = [0] * ancho * alto # Índices en la paleta de cada píxel en la imagen
for u in range (numUDGs):
posPorCol = u * 8
posPorFila = 0
for fila in range (8):
b = carga_int1() # Fila actual
for indiceBit in range (8): # Cada bit de la fila actual
imagen[posPorFila + posPorCol + indiceBit] = 0 if b & (2 ** (7 - indiceBit)) else 1
posPorFila += ancho
return imagen, paletaBN
def da_paletas_del_formato ():
"""Devuelve un diccionario con las paletas del formato de base de datos gráfica, que será lista vacía para los modos que soportan paleta variable"""
if modo_gfx == 'CGA':
return {'CGA': [paleta1b, paleta2b]}
if modo_gfx == 'EGA':
return {'EGA': [paletaEGA]}
if modo_gfx == 'PCW':
return {'PCW': [paletaPCW]}
if modo_gfx in ('ST', 'VGA'):
return {'CGA': [paleta1b, paleta2b], 'EGA': [paletaEGA], 'ST/VGA': []}
def elimina_recurso (numRecurso):
"""Elimina los datos del recurso de número dado"""
recurso = recursos[numRecurso]
if len (pos_recursos[recurso['desplazamiento']]) > 1: # El contenido de este recurso era usado por más de un recurso
pos_recursos[recurso['desplazamiento']].remove (numRecurso)
else: # El contenido de este recurso era único
del pos_recursos[recurso['desplazamiento']]
recursos[numRecurso] = None
def guarda_bd_pics (fichero, ordenAmiga = True):
"""Exporta la base de datos gráfica en memoria sobre el fichero dado"""
# TODO: completar para el formato de DMG versión 3+
bajo_nivel_cambia_endian (le)
bajo_nivel_cambia_sal (fichero)
if le:
guarda_int2 = guarda_int2_le
guarda_int4 = guarda_int4_le
else:
guarda_int2 = guarda_int2_be
guarda_int4 = guarda_int4_be
# Guardamos la plataforma y el modo gráfico
if version > 1:
if modo_gfx == 'ST':
guarda_int2 (768) # Plataforma Amiga/Atari ST
else:
guarda_int2 (65535) # Plataforma PC
guarda_int2 (0) # No se especifica modo gráfico
else: # version == 1
if modo_gfx == 'ST':
guarda_int2 (4) # Plataforma Amiga/Atari ST
guarda_int2 (0) # No tiene modo gráfico
else:
guarda_int2 (0) # Plataforma PC/PCW
if modo_gfx == 'EGA':
guarda_int2 (13) # Modo gráfico EGA
else:
guarda_int2 (4) # Modo gráfico CGA/PCW
# Guardamos el número de imágenes únicas
guarda_int2 (len (pos_recursos))
if version > 1: # Dejamos espacio para la longitud de la base de datos gráfica
guarda_int4 (0)
# Dejamos espacio para las cabeceras de los recursos
if sys.version_info [0] < 3:
fichero.write (chr (0) * long_cabecera_rec * 256)
else:
fichero.write (bytes ([0] * long_cabecera_rec * 256))
# Guardamos la posición actual para empezar a guardar los datos de los recursos desde aquí
desplActual = fichero.tell()
# Ordenamos los recursos de menor a mayor desplazamiento dentro de la base de datos gráfica
desplazamientos = sorted (pos_recursos.keys())
if desplazamientos: # Ponemos los números negativos (los de nuevos recursos) al final
ultimoNegativo = -1
while ultimoNegativo + 1 < len (desplazamientos) and desplazamientos[ultimoNegativo + 1] < 0:
ultimoNegativo += 1
desplNegativos = desplazamientos[:ultimoNegativo + 1]
desplNegativos.reverse()
desplazamientos = desplazamientos[ultimoNegativo + 1:] + desplNegativos
# Guardamos cada recurso y su cabecera en orden
for desplazamiento in desplazamientos:
fichero.seek (desplActual)
numRecursos = pos_recursos[desplazamiento]
recurso = recursos[numRecursos[0]]
# TODO: resto de formatos aparte de PCW y DMG3+
# TODO: no forzar compresión de imágenes
if version > 1:
imagen, repetir = comprimeImagenDMG3 (recurso['imagen'], forzarRLE = True)
else:
imagen, repetir = comprimeImagenPlanar (recurso['imagen'], recurso['dimensiones'][0], True, forzarRLE = True)
# Guardamos el contenido del recurso
# Guardamos el ancho de la imagen y la bandera de compresión RLE
rle = 128 if len (imagen) else 0 # 0 no comprimir, 128 comprimir
lsbAncho = recurso['dimensiones'][0] & 255 # LSB de la anchura de la imagen
msbAnchoMasRLE = ((recurso['dimensiones'][0] >> 8) & 127) + rle # MSB de la anchura de la imagen + bit de compresión RLE
if le:
guarda_int1 (lsbAncho)
guarda_int1 (msbAnchoMasRLE)
else:
guarda_int1 (msbAnchoMasRLE)
guarda_int1 (lsbAncho)
guarda_int2 (recurso['dimensiones'][1]) # Altura de la imagen
# TODO: no compresión de imágenes
longImagenRLE = len (imagen) # Longitud de la imagen incluyendo la información para compresión RLE
if rle:
longImagenRLE += 2 if modo_gfx == 'ST' or version > 1 else 5
guarda_int2 (longImagenRLE) # Longitud de la imagen codificada
# Guardamos la información para compresión RLE
if rle:
if modo_gfx == 'ST' or version > 1:
bits = 0 # Máscara de colores que se repetirán
for indiceBit in range (16):
if indiceBit in repetir:
bits += 2 ** indiceBit
guarda_int2 (bits)
else:
guarda_int1 (len (repetir)) # Número de secuencias que se repetirán
for i in range (4):
if i < len (repetir):
guarda_int1 (repetir[i])
else:
guarda_int1 (0)
# Guardamos datos de la imagen en sí
fichero.write (bytes (bytearray (imagen)))
# Guardamos la posición actual para guardar el contenido del siguiente recurso aquí
desplSiguiente = fichero.tell()
# Guardamos la cabecera de cada uno de los recursos con este mismo desplazamiento
for numRecurso in numRecursos:
cabRecurso = long_cabecera + (long_cabecera_rec * numRecurso)
fichero.seek (cabRecurso) # Vamos al desplazamiento de la cabecera del recurso
guarda_int4 (desplActual)
recurso = recursos[numRecurso]
guarda_int2 (recurso['banderasInt'])
guarda_int2 (recurso['posicion'][0]) # Coordenada X donde dibujar la imagen
guarda_int2 (recurso['posicion'][1]) # Coordenada Y donde dibujar la imagen
if 'cambioPaleta' in recurso:
guarda_int1 (recurso['cambioPaleta'][0])
guarda_int1 (recurso['cambioPaleta'][1])
guardaPaletas (recurso, ordenAmiga)
desplActual = desplSiguiente
# Guardamos la longitud de la base de datos gráfica
if version > 1:
fichero.seek (6)
guarda_int4 (desplActual)
def guarda_portada_amiga (imagen, paleta, fichero):
"""Guarda una portada de Amiga en el fichero abierto dado con la imagen dada como lista de índices"""
bajo_nivel_cambia_sal (fichero)
guarda_int2_be (0)
guardaPaleta16 (paleta, 4)
ancho, alto = resolucion_por_modo['ST']
guardaImagenPlanar (imagen, ancho, alto, 4)
def recurso_es_unico (numRecurso):
"""Devuelve si el contenido del recurso es único, o si por el contrario es usado por varios recursos"""
return len (pos_recursos[recursos[numRecurso]['desplazamiento']]) == 1
# Funciones de apoyo de alto nivel
def cargaImagenAmiga (repetir, tamImg):
"""Carga y devuelve una imagen de Amiga/Atari ST con el formato que usan DMG 1 y DMG 3+ en imágenes sin compresión, como lista de índices en la paleta"""
color = None # Índice de color del píxel actual
imagen = [] # Índices en la paleta de cada píxel en la imagen
numPlanos = 4
while len (imagen) < tamImg: # Mientras quede imagen por procesar
colores = [0] * 8
for plano in range (numPlanos):
b = carga_int1() # Byte actual
for indiceBit in range (8):
bit = b & (2 ** indiceBit)
colores[7 - indiceBit] += (2 ** plano) if bit else 0
for pixel in range (8): # Cada píxel en el grupo
if color == None:
color = colores[pixel]
if color in repetir:
continue # El número de repeticiones vendrá en el valor del siguiente píxel
if color in repetir:
repeticiones = min (colores[pixel] + 1, tamImg - len (imagen))
else:
repeticiones = 1
imagen += [color] * repeticiones
color = None
if len (imagen) == tamImg:
break
return imagen
def cargaImagenAtari (fichero):
"""Carga y devuelve una imagen de Atari ST (en formato PIX de SWAN) junto con su paleta, como índices en la paleta de cada píxel"""
bajo_nivel_cambia_ent (fichero)
fichero.seek (4)
paleta = cargaPaleta16 (3)
fichero.seek (128)
imagen = [] # Índices en la paleta de cada píxel en la imagen
numPlanos = 4
tamImg = 320 * 96
while len (imagen) < tamImg: # Mientras quede imagen por procesar
colores = [0] * 16
for plano in range (numPlanos):
b = carga_int2_be() # Doble byte actual
for indiceBit in range (16):
bit = b & (2 ** indiceBit)
colores[15 - indiceBit] += (2 ** plano) if bit else 0
for pixel in range (16): # Cada píxel en el grupo
imagen += [colores[pixel]]
if len (imagen) == tamImg:
break
return imagen, paleta
def cargaImagenCGA (ancho, repetir, tamImg):
"""Carga y devuelve una imagen CGA de DMG 1, como lista de índices en la paleta"""
fila = [] # Índices en la paleta de cada píxel en la fila actual
imagen = [] # Índices en la paleta de cada píxel en la imagen
izqAder = True # Sentido de procesado de píxeles de la fila actual
while len (imagen) < tamImg: # Mientras quede imagen por procesar
b = carga_int1() # Byte actual
if b in repetir:
repeticiones = carga_int1()
else:
repeticiones = 1
pixeles = [b >> 6, (b >> 4) & 3, (b >> 2) & 3, b & 3] # Color de los cuatro píxeles actuales
if izqAder: # Sentido de izquierda a derecha
fila += pixeles * repeticiones # Añadimos al final
else: # Sentido de derecha a izquierda
fila = (pixeles * repeticiones) + fila # Añadimos al principio
while len (fila) >= ancho: # Al repetirse, se puede exceder la longitud de una fila
if izqAder: # Sentido de izquierda a derecha
imagen += fila[0:ancho]
fila = fila[ancho:]
else: # Sentido de derecha a izquierda
imagen += fila[-ancho:]
fila = fila[:-ancho]
if repetir: # Si la imagen usa compresión RLE
izqAder = not izqAder
return imagen
def cargaImagenDMG3DOS (le, numImagen, repetir, tamImg):
"""Carga una imagen en formato de DMG 3+ para DOS, que también se usa para imágenes de Amiga/Atari ST comprimidas, y la devuelve como lista de índices en la paleta. Devuelve un mensaje de error si falla"""
cargar = 1 if le else 4 # Cuántos bytes de valores cargar cada vez, tomando primero el último cargado
color = None # Índice de color del píxel actual
imagen = [] # Índices en la paleta de cada píxel en la imagen
valores = [] # Valores (índices de color y contador de repeticiones) pendientes de procesar, en orden
while len (imagen) < tamImg: # Mientras quede imagen por procesar
if not valores:
try:
for i in range (cargar):
b = carga_int1() # Byte actual
valores = [b & 15, b >> 4] + valores # Los 4 bits más bajos primero, y luego los 4 más altos
except:
return 'Imagen %d incompleta. ¿Formato incorrecto?' % numImagen
if color == None:
color = valores.pop (0)
continue # Por si hay que cargar un nuevo byte
if color in repetir:
repeticiones = valores.pop (0) + 1
else:
repeticiones = 1
imagen += [color] * repeticiones
color = None
return imagen
def cargaImagenPIXenBN (fichero):
"""Carga y devuelve una imagen PIX de SWAN en blanco y negro junto con su paleta, como índices en la paleta de cada píxel"""
fichero.seek (8)
bajo_nivel_cambia_ent (fichero)
ancho = 304
alto = 64
return cargaImagenPlanar (ancho, alto, 1, 0, [None], ancho * alto, invertir = False), (paletaBN[1], paletaBN[0])
def cargaImagenPlanar (ancho, alto, numPlanos, numImg, repetir, tamImg, invertir = True):
"""Carga una imagen EGA o PCW de DMG 1, modos gráficos PCW monocromo y Amiga y EGA en orden planar con cuatro planos de bit de color enteros consecutivos, y la devuelve como lista de índices en la paleta. Devuelve un mensaje de error si falla"""
imagen = [0] * tamImg # Índices en la paleta de cada píxel en la imagen
izqAder = True # Sentido de procesado de píxeles de la fila actual
repeticiones = 0
for plano in range (numPlanos):
bitsFila = []
numFila = 0
while numFila < alto:
if not repeticiones:
try:
b = carga_int1() # Byte actual
if b in repetir:
repeticiones = carga_int1()
if repeticiones < 1:
return 'Valor inesperado (0) para el número de repeticiones de RLE, en la imagen ' + str (numImg)
else:
repeticiones = 1
except:
return 'Imagen %d incompleta. ¿Formato incorrecto?' % numImg
bits = [] # Bits del byte actual
for indiceBit in range (7, -1, -1): # Cada bit del byte actual
bits.append (1 if b & (2 ** indiceBit) else 0)
cuantas = min (repeticiones, (ancho - len (bitsFila)) // 8) # Evitamos exceder la longitud de una fila
if izqAder or not invertir: # Sentido de izquierda a derecha
bitsFila.extend (bits * cuantas) # Añadimos al final
else: # Sentido de derecha a izquierda
bitsReversa = bits[::-1]
bitsFila.extend (bitsReversa * cuantas) # Añadimos al final, pero con los bits invertidos
repeticiones -= cuantas
if len (bitsFila) == ancho: # Al repetir no se excede la longitud de una fila
if numPlanos == 1 and not repetir: # Modo PCW sin compresión RLE
bytesEnFila = ancho // 8
bloquesEnFila = bytesEnFila // 8 # Bloques de 8 bytes por cada fila
for indiceByte in range (bytesEnFila):
byte = bitsFila[indiceByte * 8 : (indiceByte * 8) + 8]
numBloqueDest = numFila // 8 # Índice del bloque de 8 filas, en destino
numFilaDest = (indiceByte % 8) # Índice de fila dentro del bloque, en destino
numByteDest = ((numFila % 8) * bloquesEnFila) + (indiceByte // 8) # Índice del byte dentro de la fila, en destino
primerPixel = (numBloqueDest * ancho * 8) + (numFilaDest * ancho) + (numByteDest * 8) # Índice del primer píxel del byte, en destino
for indiceBit in range (8):
bit = byte[indiceBit]
imagen[primerPixel + indiceBit] += (2 ** plano) if bit else 0
elif izqAder or not invertir: # Sentido de izquierda a derecha
primerPixel = (numFila * ancho) # Índice del primer píxel del byte
for indiceBit in range (ancho):
bit = bitsFila[indiceBit]
imagen[primerPixel + indiceBit] += (2 ** plano) if bit else 0
else: # Sentido de derecha a izquierda
ultimoPixel = (numFila * ancho) + ancho - 1 # Índice del último píxel del byte
for indiceBit in range (ancho):
bit = bitsFila[indiceBit]
imagen[ultimoPixel - indiceBit] += (2 ** plano) if bit else 0
bitsFila = []
if repetir: # Si la imagen usa compresión RLE
izqAder = not izqAder
numFila += 1
return imagen
def cargaPortadaAmiga (fichero):
"""Carga y devuelve una portada de Amiga junto con su paleta, como índices en la paleta de cada píxel"""
bajo_nivel_cambia_ent (fichero)
fichero.seek (2)
paleta = cargaPaleta16 (4, True)
ancho = 320
alto = 200
return cargaImagenPlanar (ancho, alto, 4, 0, [], ancho * alto), paleta
def cargaPortadaAtari (fichero):
"""Carga y devuelve una portada de Atari ST junto con su paleta, como índices en la paleta de cada píxel"""
bajo_nivel_cambia_ent (fichero)
fichero.seek (2)
paleta = cargaPaleta16 (3)
imagen = [] # Índices en la paleta de cada píxel en la imagen
numPlanos = 4
tamImg = 320 * 200
while len (imagen) < tamImg: # Mientras quede imagen por procesar
colores = [0] * 16
for plano in range (numPlanos):
b = carga_int2_be() # Doble byte actual
for indiceBit in range (16):
bit = b & (2 ** indiceBit)
colores[15 - indiceBit] += (2 ** plano) if bit else 0
for pixel in range (16): # Cada píxel en el grupo
imagen += [colores[pixel]]
if len (imagen) == tamImg:
break
return imagen, paleta
def cargaPortadaCGA (fichero, longFichero):
"""Carga y devuelve una portada CGA junto con su paleta, como lista de índices en la paleta de cada fila"""
bajo_nivel_cambia_ent (fichero)
ancho = 320
alto = 200
fila = [] # Índices en la paleta de cada píxel en la fila actual
imagen = [[]] * alto # Lista de filas, cada una con los índices en la paleta de cada píxel en ella
tamFila = ancho // 4 # Tamaño de una fila en bytes
for i in range (alto): # Cada fila de la imagen
# Primero van las filas pares, y luego las impares
numFila = i * 2
if numFila >= alto:
if numFila == alto and longFichero == 16384: # Se acaba de leer la primera mitad de la imagen
fichero.seek (8192)
numFila -= alto - 1
for indiceByte in range (tamFila): # Cada byte de la fila
b = carga_int1() # Byte actual
fila += [b >> 6, (b >> 4) & 3, (b >> 2) & 3, b & 3] # Color de los cuatro píxeles actuales
imagen[numFila] = fila
fila = []
if longFichero == 16384:
fichero.seek (16382)
fondo = carga_int1() & 15
paleta = list (paleta1s if carga_int1() & 1 else paleta2s)
paleta[0] = paletaEGA[fondo]
else:
paleta = list (paleta1b)
return imagen, paleta
def cargaPortadaEGA (fichero):
"""Carga y devuelve una portada EGA junto con su paleta, como índices en la paleta de cada píxel"""
bajo_nivel_cambia_ent (fichero)
fichero.seek (1) # El primer byte es otra cosa (¿tal vez el modo gráfico?)
ancho = 320
alto = 200
return cargaImagenPlanar (ancho, alto, 4, 0, [], ancho * alto), paletaEGA
def cargaPortadaVGA (fichero):
"""Carga y devuelve una portada VGA junto con su paleta, como índices en la paleta de cada píxel"""
bajo_nivel_cambia_ent (fichero)
fichero.seek (1) # El primer byte es otra cosa (¿tal vez el modo gráfico?)
paleta = cargaPaleta16_6bpc()
ancho = 320
alto = 200
return cargaImagenPlanar (ancho, alto, 4, 0, [], ancho * alto), paleta
def cargaPaleta16 (bpc, portadaAmiga = False):
"""Carga y devuelve una paleta de 16 colores, con el número de bits por componente de color dado"""
bytesPaleta = []
for color in range (16):
bytesPaleta.append ((carga_int1(), carga_int1())) # Rojo primero, y luego verde y azul juntos
ordenAmiga = True # Si el cuarto bit de componente de color es el más significativo
if not portadaAmiga and bpc == 4 and version > 1 and modo_gfx == 'ST':
# Es paleta de imagen de BD gráfica DMG 3+ de Amiga/Atari ST con 4 bpc
# Vemos si los 4 bytes de la tabla de conversión de colores para CGA marcan orden de Amiga, si valen 0xDAADDAAD
tablaCGA = (carga_int1(), carga_int1(), carga_int1(), carga_int1())
if tablaCGA != marca_amiga:
ordenAmiga = False
valorMax = (2 ** bpc) - 1 # Valor máximo en componentes de color
distancia = 255. / valorMax # Distancia para equiespaciar de 0 a 255
paleta = []
for color in range (16):
rojo, veaz = bytesPaleta[color]
if ordenAmiga:
rojo = (rojo & valorMax) * distancia
verde = ((veaz >> 4) & valorMax) * distancia
azul = (veaz & valorMax) * distancia
else: # Los bits de las componentes de color están en el orden de las paletas de Atari STe
rojo = ((rojo & 7) * 2 + (1 if rojo & 8 else 0)) * distancia
verde = (((veaz >> 4) & 7) * 2 + (1 if veaz & 128 else 0)) * distancia
azul = ((veaz & 7) * 2 + (1 if veaz & 8 else 0)) * distancia
paleta.append ((int (round (rojo)), int (round (verde)), int (round (azul))))
return paleta
def cargaPaleta16_6bpc ():
"""Carga y devuelve una paleta de 16 colores, con 6 bits por componente de color, y cada componente en un byte aparte"""
valorMax = (2 ** 6) - 1 # Valor máximo en componentes de color
distancia = 255. / valorMax # Distancia para equiespaciar de 0 a 255
paleta = []
for color in range (16):
rojo = carga_int1() * distancia
verde = carga_int1() * distancia
azul = carga_int1() * distancia
paleta.append ((int (round (rojo)), int (round (verde)), int (round (azul))))
return paleta
def cargaRecursos ():
"""Carga todos los gráficos y sonidos de la base de datos gráfica. Devuelve un mensaje de error si falla"""
errores = ''
pos_recursos.clear()
del recursos[:]
for numRecurso in range (256):
cabRecurso = long_cabecera + (long_cabecera_rec * numRecurso) # Desplazamiento de la cabecera del recurso
posRecurso = carga_desplazamiento4 (cabRecurso)
if not posRecurso:
recursos.append (None)
continue # Ningún recurso con ese número
banderas = set()
flags = carga_int2()
if flags & 1:
banderas.add ('flotante')
if flags & 2:
banderas.add ('residente')
if (flags & 4 and version > 1) or (flags & 128 and version < 3):
banderas.add ('cgaPaleta1')
if flags & 256 and version > 1:
banderas.add ('sonido')
recurso = {'banderas': banderas, 'banderasInt': flags, 'posicion': (carga_int2(), carga_int2())}
if modo_gfx == 'ST' or version > 1:
recurso['cambioPaleta'] = (carga_int1(), carga_int1())
if long_paleta:
recurso['paleta'] = cargaPaleta16 (4 if version > 1 and modo_gfx == 'ST' else 3)
elif modo_gfx == 'CGA':
recurso['paleta'] = paleta1b if 'cgaPaleta1' in banderas else paleta2b
elif modo_gfx == 'EGA':
recurso['paleta'] = paletaEGA
elif modo_gfx == 'PCW':
recurso['paleta'] = paletaPCW
if 'sonido' in banderas:
recursos.append (None)
continue # TODO: manejo de recursos de sonido no implementado
# Detectamos imágenes con el mismo desplazamiento para ahorrar memoria y reducir tiempo de carga
recurso['desplazamiento'] = posRecurso
if posRecurso in pos_recursos:
recurso['dimensiones'] = recursos[pos_recursos[posRecurso][0]]['dimensiones']
recurso['imagen'] = recursos[pos_recursos[posRecurso][0]]['imagen']
recursos.append (recurso)
pos_recursos[posRecurso].append (numRecurso)
continue
pos_recursos[posRecurso] = [numRecurso]
fichero.seek (posRecurso) # Saltamos a donde está el recurso (en este caso, imagen)
if le:
ancho = carga_int1() # LSB de la anchura de la imagen
valor = carga_int1()
else:
valor = carga_int1()
ancho = carga_int1() # LSB de la anchura de la imagen
ancho += (valor & 127) * 256
if ancho == 0 or ancho % 8:
return 'El ancho de la imagen %d no es mayor que 0 y múltiplo de 8, vale %d' % (numRecurso, ancho)
rle = valor & 128
alto = carga_int2() # Altura de la imagen
if alto == 0 or alto % 8:
return 'El alto de la imagen %d no es mayor que 0 y múltiplo de 8, vale %d' % (numRecurso, alto)
recurso['dimensiones'] = (ancho, alto)
# Secuencias que se repiten para la compresión RLE
repetir = []
fichero.seek (2, 1) # Saltamos valor de longitud de la imagen
if rle:
if modo_gfx == 'ST' or version > 1:
bits = carga_int2() # Máscara de colores que se repetirán
for indiceBit in range (16):
if bits & (2 ** indiceBit):
repetir.append (indiceBit)
else:
b = carga_int1() # Número de secuencias que se repetirán
if b > 4:
return 'Valor inesperado para el número de secuencias que se repetirán: %d para la imagen %d' % (b, numRecurso)
for i in range (4):
if i < b:
repetir.append (carga_int1())
else:
fichero.seek (1, 1)
# Carga de la imagen en sí
imagen = [] # Índices en la paleta de cada píxel en la imagen
tamImg = ancho * alto # Tamaño en píxeles (y bytes) que tendrá la imagen
if modo_gfx == 'CGA':
imagen = cargaImagenCGA (ancho, repetir, tamImg)
elif modo_gfx in ('EGA', 'PCW'):
imagen = cargaImagenPlanar (ancho, alto, 4 if modo_gfx == 'EGA' else 1, numRecurso, repetir, tamImg)
elif version < 3 or (modo_gfx == 'ST' and not rle): # Formato de Amiga/Atari ST de DMG 1 y de DMG 3+ sin compresión
imagen = cargaImagenAmiga (repetir, tamImg)
else: # Formato de DMG3+ de DOS o de Amiga/Atari ST comprimido
imagen = cargaImagenDMG3DOS (le, numRecurso, repetir, tamImg)
if type (imagen) == str: # Ha ocurrido algún error al tratar de cargar la imagen
errores += ('\n' if errores else '') + imagen
recursos.append (None)
else: # Imagen cargada correctamente
recurso['imagen'] = imagen
recursos.append (recurso)
if errores:
return errores
def comprimeImagenDMG3 (imagen, forzarRLE = False):
"""Devuelve una lista de bytes como enteros con la compresión RLE óptima en el formato de DMG 3+ (el de DMG 1 para Amiga y ST), y una lista de bytes como enteros con las combinaciones que se repiten.
Si forzarRLE es falso y comprimir la imagen no ahorrará espacio, devuelve las dos listas vacías"""
# Primero calculamos cuánto se ahorra con cada secuencia de bits
ahorros = {} # Cuánto ahorrará cada secuencia
ocurrencias = 1 # Número de veces seguidas que se ha encontrado el último valor
valorAnterior = imagen[0]
for i in range (1, len (imagen) + 1):
if i == len (imagen): # Para procesar también el último valor
valor = None
else:
valor = imagen[i]
if valor == valorAnterior:
ocurrencias += 1
continue
if valorAnterior not in ahorros:
ahorros[valorAnterior] = 0
while ocurrencias:
cuantos = min (16, ocurrencias)
ahorros[valorAnterior] += cuantos - 2
ocurrencias -= cuantos
ocurrencias = 1
valorAnterior = valor
ahorroTotal = 0
mejores = []
for valor in ahorros:
if ahorros[valor] < 1:
continue
mejores.append (valor)
ahorroTotal += ahorros[valor]
if not forzarRLE and ahorroTotal < 2:
return [], [] # La compresión RLE en esta imagen no ahorra nada
# Realizamos la compresión RLE
comprimida = [] # Imagen comprimida por índices en la paleta
numFila = 0
ocurrencias = 1 # Número de veces seguidas que se ha encontrado el último valor
valorAnterior = imagen[0]
for i in range (1, len (imagen) + 1):
if i == len (imagen): # Para procesar también el último valor
valor = None
else:
valor = imagen[i]
if valorAnterior not in mejores:
comprimida.append (valorAnterior)
valorAnterior = valor
continue
if valor == valorAnterior:
ocurrencias += 1
continue
while ocurrencias:
cuantos = min (16, ocurrencias)
comprimida.append (valorAnterior)
comprimida.append (cuantos - 1)
ocurrencias -= cuantos
ocurrencias = 1
valorAnterior = valor
# Convertimos a bytes la secuencia comprimida
comprimidaPorBytes = []
bytesEnGrupo = 1 if le else 4 # Cuántos bytes se guardan cada vez
nibblesEnGrupo = bytesEnGrupo * 2
for c in range (0, len (comprimida), nibblesEnGrupo):
grupoBytes = []
for g in range (bytesEnGrupo):
indiceNibble = c + (g * 2)
if indiceNibble + 1 >= len (comprimida):
if indiceNibble < len (comprimida):
grupoBytes.append (comprimida[indiceNibble])
while len (grupoBytes) < bytesEnGrupo:
grupoBytes.append (0)
break
grupoBytes.append (comprimida[indiceNibble] + (comprimida[indiceNibble + 1] << 4))
comprimidaPorBytes.extend (grupoBytes[::-1])
return comprimidaPorBytes, mejores
def comprimeImagenPlanar (imagen, anchoFilaEnBits, invertirBits, forzarRLE = False):
"""Devuelve una lista de bytes como enteros con la compresión RLE óptima para imágenes planares en el formato de DMG 1, y una lista de bytes como enteros con las combinaciones que se repiten.
Si forzarRLE es falso y comprimir la imagen no ahorrará espacio, devuelve las dos listas vacías"""
# TODO: soporte de mayor profundidad de color, como será necesario en los demás formatos aparte de PCW
# Primero calculamos cuánto se ahorra con cada secuencia de bits
ahorros = {} # Cuánto ahorrará cada secuencia
izqAder = True # Sentido de procesado de píxeles de la fila actual
numFila = 0 # Número de fila que se está procesando
ocurrencias = 1 # Número de veces seguidas que se ha encontrado el último valor
valorAnterior = imagen[:8]
for primerBit in range (8, len (imagen) + 8, 8):
if primerBit % anchoFilaEnBits == 0:
izqAder = not izqAder
numFila += 1
if primerBit == len (imagen): # Para procesar también el último valor
valor = []
else:
if izqAder:
valor = imagen[primerBit : primerBit + 8]
else:
ultimoBit = ((numFila + 1) * anchoFilaEnBits) - (primerBit % anchoFilaEnBits)
valor = imagen[ultimoBit - 8 : ultimoBit]
if invertirBits:
valor = valor[::-1]
if valor == valorAnterior:
ocurrencias += 1
continue
valorComoByte = 0 # Valor anterior como byte
for indiceBit in range (8):
valorComoByte += 2 ** indiceBit if valorAnterior[indiceBit] else 0
if valorComoByte not in ahorros:
ahorros[valorComoByte] = 0
while ocurrencias:
cuantos = min (255, ocurrencias)
ahorros[valorComoByte] += cuantos - 2
ocurrencias -= cuantos
ocurrencias = 1
valorAnterior = valor
ahorroTotal = 0
mejores = []
while ahorros and len (mejores) < 4:
mejor = max (ahorros, key = ahorros.get)
if ahorros[mejor] < 1:
break
mejores.append (mejor)
ahorroTotal += ahorros[mejor]
del ahorros[mejor]
if not forzarRLE and ahorroTotal < 5:
return [], [] # La compresión RLE en esta imagen no ahorra nada
# Realizamos la compresión RLE
comprimida = []
izqAder = True
numFila = 0
ocurrencias = 1 # Número de veces seguidas que se ha encontrado el último valor
valorAnterior = imagen[:8]
for primerBit in range (8, len (imagen) + 8, 8):
if primerBit % anchoFilaEnBits == 0:
izqAder = not izqAder
numFila += 1
if primerBit == len (imagen): # Para procesar también el último valor
valor = []
else:
if izqAder:
valor = imagen[primerBit:primerBit + 8]
else:
ultimoBit = ((numFila + 1) * anchoFilaEnBits) - (primerBit % anchoFilaEnBits)
valor = imagen[ultimoBit - 8 : ultimoBit]
if invertirBits:
valor = valor[::-1]
valorComoByte = 0 # Valor anterior como byte
for indiceBit in range (8):
valorComoByte += 2 ** indiceBit if valorAnterior[indiceBit] else 0
if valorComoByte not in mejores:
comprimida.append (valorComoByte)
valorAnterior = valor
continue
if valor == valorAnterior:
ocurrencias += 1
continue
while ocurrencias:
cuantos = min (255, ocurrencias)
comprimida.append (valorComoByte)
comprimida.append (cuantos)
ocurrencias -= cuantos
ocurrencias = 1
valorAnterior = valor
return comprimida, mejores
def detectaFuente8 (fichero):
"""Detecta con heurísticas la posición de memoria que con mayor probabilidad contiene una fuente de 8x8 en el fichero abierto dado, o None si no detectó ninguna o no pudo descartar hasta quedarse con una sola"""
# Cargamos primero todo el contenido del fichero a memoria para buscar allí más rápidamente
fichero.seek (0)
memoria = fichero.read()
espacio = b'\x00' * 8 # Secuencia de un carácter de espacio en blanco
medioEsp = b'\x00' * 4 # Secuencia de medio carácter de espacio en blanco
inicio = 0 # Posición donde inicia la búsqueda
posibles = set() # Posiciones que la heurística considera posibles
posicion = memoria.find (espacio) # Posición donde se ha encontrado (o no) la secuencia
# Paso de descarte 1
while posicion > -1 and posicion + 768 <= len (memoria): # En posicion está la secuencia de posible carácter de espacio en blanco
# Con heurísticas, averiguamos si hay algo que parece una fuente de 8x8 en posicion
inicioComa = posicion + 12 * 8 # Posición donde empezaría el carácter ',' en la fuente
caracterComa = memoria[inicioComa:inicioComa + 8]
inicioPunto = inicioComa + 2 * 8 # Posición donde empezaría el carácter '.' en la fuente
caracterPunto = memoria[inicioPunto:inicioPunto + 8]
inicioNumeros = inicioPunto + 2 * 8 # Posición donde empezaría el carácter '0' en la fuente
inicioMayusc = inicioNumeros + 17 * 8 # Posición donde empezaría el carácter 'A' en la fuente
inicioMinusc = inicioMayusc + 32 * 8 # Posición donde empezaría el carácter 'a' en la fuente
if (caracterComa[:4] == medioEsp and caracterPunto[:4] == medioEsp # La parte superior de la coma y el punto están en blanco
and caracterComa != espacio and caracterPunto != espacio): # Pero su parte inferior no está en blanco
# Vemos que no haya caracteres en blanco en las posiciones de los números y letras
rangos = ((inicioNumeros, 10), (inicioMayusc, 26), (inicioMinusc, 26))
for inicioRango, cuantos in rangos:
for inicioCaracter in range (inicioRango, inicioRango + cuantos * 8, 8):
if memoria[inicioCaracter:inicioCaracter + 8] == espacio: # El carácter de esta posición está en blanco
break
else:
continue # Ninguno estaba en blanco
break # Algún carácter en blanco
else:
posibles.add (posicion)
inicio = posicion + 1
posicion = memoria.find (espacio, inicio)
if traza:
prn ('Posiciones posibles para fuente de 8x8 en el paso 1:', posibles, file = sys.stderr)
# Paso de descarte 2