cursos:ensamblador:compresion_rle

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
cursos:ensamblador:compresion_rle [06-01-2024 16:45] – [ZX0: el algoritmo de compresión definitivo] sromerocursos:ensamblador:compresion_rle [19-01-2024 12:35] (actual) – [Programa de ejemplo de descompresión] sromero
Línea 12: Línea 12:
  
  En resumen:  En resumen:
-\\ + 
-\\+\\ 
   * Proceso de **compresión**: \\ \\ Datos (7KB) -> COMPRESION EN PC -> Datos_comprimidos (2KB)   * Proceso de **compresión**: \\ \\ Datos (7KB) -> COMPRESION EN PC -> Datos_comprimidos (2KB)
-\\+\\ 
   * Proceso de **descompresión**: \\ \\ Datos_comprimidos (2KB) -> DESCOMPRESION AL VUELO -> Datos (7KB).   * Proceso de **descompresión**: \\ \\ Datos_comprimidos (2KB) -> DESCOMPRESION AL VUELO -> Datos (7KB).
-\\+\\ 
  
  Los procesos de COMPRESION y DESCOMPRESION son funciones que reciben como entrada los datos en crudo o los datos comprimidos (respectivamente) y producen como salida los datos comprimidos o los datos originales.  Los procesos de COMPRESION y DESCOMPRESION son funciones que reciben como entrada los datos en crudo o los datos comprimidos (respectivamente) y producen como salida los datos comprimidos o los datos originales.
Línea 26: Línea 26:
  
  
-\\+\\ 
 ===== Fundamentos de la compresión RLE ===== ===== Fundamentos de la compresión RLE =====
  
Línea 59: Línea 59:
 </code> </code>
  
-\\ +\\  
-\\+\\ 
  Ahora bien ... ¿qué tipo de marcador podemos utilizar?  Ahora bien ... ¿qué tipo de marcador podemos utilizar?
  
-\\+\\ 
  En el algoritmo RLE **se utilizan como marcadores de repetición los 2 bits superiores del dato**. Estos bits, el número 7 y el número 6, tienen el siguiente valor:  En el algoritmo RLE **se utilizan como marcadores de repetición los 2 bits superiores del dato**. Estos bits, el número 7 y el número 6, tienen el siguiente valor:
  
Línea 131: Línea 131:
 </code> </code>
  
-\\+\\ 
   * 1 -> (<192) = No está comprimido\\ -> El 1 lo copio en Descomprimido.   * 1 -> (<192) = No está comprimido\\ -> El 1 lo copio en Descomprimido.
  
Línea 151: Línea 151:
  
   * 6 -> (<192) = No está comprimido\\ -> El 6 lo copio en Descomprimido.   * 6 -> (<192) = No está comprimido\\ -> El 6 lo copio en Descomprimido.
-\\+\\ 
  Nótese que dado que utilizamos los bits 0 al 5 para indicar el número de repeticiones (puesto que el 6 y el 7 son marcadores RLE), eso quiere decir que un dato "comprimido" sólo puede representar un máximo de 63 repeticiones.  Nótese que dado que utilizamos los bits 0 al 5 para indicar el número de repeticiones (puesto que el 6 y el 7 son marcadores RLE), eso quiere decir que un dato "comprimido" sólo puede representar un máximo de 63 repeticiones.
  
Línea 160: Línea 160:
  **255, 0, 199, 0**  **255, 0, 199, 0**
  
-\\+\\ 
  
 ===== Los datos que son mayores de 191 ===== ===== Los datos que son mayores de 191 =====
Línea 203: Línea 203:
  
  
-\\+\\ 
  
 ===== Resumen sobre la compresión ===== ===== Resumen sobre la compresión =====
Línea 209: Línea 209:
  Antes de ver el pseudocódigo y las rutinas descompresoras y compresoras, vamos a resumir lo visto hasta ahora.  Antes de ver el pseudocódigo y las rutinas descompresoras y compresoras, vamos a resumir lo visto hasta ahora.
  
-\\+\\ 
   * La **compresión** es un proceso que convierte una serie de datos de tamaño N en otra serie de datos de tamaño M (siendo M menor que N), mediante un determinado algoritmo de procesado de los datos originales.   * La **compresión** es un proceso que convierte una serie de datos de tamaño N en otra serie de datos de tamaño M (siendo M menor que N), mediante un determinado algoritmo de procesado de los datos originales.
  
Línea 225: Línea 225:
  
   * La **descompresión RLE** es un algoritmo de descompresión que, a partir de unos datos comprimidos con RLE, obtiene los datos originales mediante la "expansión" de los datos "de repetición" formados por "Marcador RLE con número de repeticiones", y "Dato a repetir".   * La **descompresión RLE** es un algoritmo de descompresión que, a partir de unos datos comprimidos con RLE, obtiene los datos originales mediante la "expansión" de los datos "de repetición" formados por "Marcador RLE con número de repeticiones", y "Dato a repetir".
-\\+\\ 
  
-\\+\\ 
 ===== Cómo trabajaremos con la compresión ===== ===== Cómo trabajaremos con la compresión =====
  
  A continuación vamos a ver el pseudocódigo y funciones ensamblador y C de descompresión RLE y compresión RLE. Pero antes, es importante destacar la forma en que usaremos estas funciones:  A continuación vamos a ver el pseudocódigo y funciones ensamblador y C de descompresión RLE y compresión RLE. Pero antes, es importante destacar la forma en que usaremos estas funciones:
-\\ +\\  
-\\+\\ 
   * **Compresión**: La compresión RLE de los datos (imágenes, sonido, o cualquier otro tipo de datos) se realiza en nuestro PC. Es decir, cogeremos un .SCR con una pantalla de 6912 bytes, o un .BIN con los datos gráficos de un Sprite (o cualquier otro tipo de datos), y los comprimiremos con un programa que nos dará como salida un fichero binario de datos comprimidos de (generalmente) mucho menor tamaño que el original. El programa compresor no es, pues, un programa para Spectrum, sino para el sistema en que estamos desarrollando de forma cruzada el juego. Así pues, la rutina de compresión la programaremos (o la descargaréis) en C o como fichero ejecutable para Windows, MAC o Linux.   * **Compresión**: La compresión RLE de los datos (imágenes, sonido, o cualquier otro tipo de datos) se realiza en nuestro PC. Es decir, cogeremos un .SCR con una pantalla de 6912 bytes, o un .BIN con los datos gráficos de un Sprite (o cualquier otro tipo de datos), y los comprimiremos con un programa que nos dará como salida un fichero binario de datos comprimidos de (generalmente) mucho menor tamaño que el original. El programa compresor no es, pues, un programa para Spectrum, sino para el sistema en que estamos desarrollando de forma cruzada el juego. Así pues, la rutina de compresión la programaremos (o la descargaréis) en C o como fichero ejecutable para Windows, MAC o Linux.
  
Línea 239: Línea 239:
   * **Descompresión de los datos**: La descompresión de los datos la realizamos en nuestro programa de Spectrum, mediante una rutina programada en ensamblador de Z80 o C para Z88DK. La rutina es pequeña y rápida, por lo que nos permitirá desempaquetar nuestra pantalla de 3102 bytes directamente sobre la VideoRAM (por ejemplo). Los 3102 bytes de nuestra pantalla de ejemplo, expandidos, serán los 6912 bytes originales del SCR inicial. Se dice pues que la rutina de descompresión trabaja //al vuelo// o //en tiempo real//.   * **Descompresión de los datos**: La descompresión de los datos la realizamos en nuestro programa de Spectrum, mediante una rutina programada en ensamblador de Z80 o C para Z88DK. La rutina es pequeña y rápida, por lo que nos permitirá desempaquetar nuestra pantalla de 3102 bytes directamente sobre la VideoRAM (por ejemplo). Los 3102 bytes de nuestra pantalla de ejemplo, expandidos, serán los 6912 bytes originales del SCR inicial. Se dice pues que la rutina de descompresión trabaja //al vuelo// o //en tiempo real//.
  
-\\+\\ 
  
 ===== Rutina descompresora ===== ===== Rutina descompresora =====
  
  La rutina descompresora de RLE debe recibir como parámetros los siguientes valores:  La rutina descompresora de RLE debe recibir como parámetros los siguientes valores:
-\\ +\\  
-\\+\\ 
   * Los datos comprimidos con RLE a descomprimir: Los recibiremos en forma de "puntero" o "dirección de memoria" apuntando al principio de los datos.   * Los datos comprimidos con RLE a descomprimir: Los recibiremos en forma de "puntero" o "dirección de memoria" apuntando al principio de los datos.
   * El tamaño de los datos comprimidos (necesario para el bucle de descompresión).   * El tamaño de los datos comprimidos (necesario para el bucle de descompresión).
   * Un puntero o dirección de memoria apuntando a dónde queremos descomprimir los datos.   * Un puntero o dirección de memoria apuntando a dónde queremos descomprimir los datos.
-\\+\\ 
  La rutina deberá ir cogiendo cada byte del bloque de datos comprimido y decidir si es un dato "sin comprimir" (<192) o un dato comprimido (>=192). Si es un dato sin comprimir lo copiará a la dirección de memoria destino, y si está comprimido cogerá el siguiente byte y decidirá cuántas veces debe repetirlo.  La rutina deberá ir cogiendo cada byte del bloque de datos comprimido y decidir si es un dato "sin comprimir" (<192) o un dato comprimido (>=192). Si es un dato sin comprimir lo copiará a la dirección de memoria destino, y si está comprimido cogerá el siguiente byte y decidirá cuántas veces debe repetirlo.
  
Línea 298: Línea 298:
 <code c> <code c>
 //------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
-void RLE_decompress_C( unsigned char *src, unsigned char *dst, int length )+void RLE_decompress_C(unsigned char *src, unsigned char *dst, int length)
 { {
   int i;   int i;
   unsigned char b1, b2, j;   unsigned char b1, b2, j;
  
-  for( i=0; i<length; i++)+  for (i=0; i<length; i++)
   {   {
      b1 = *src++;      b1 = *src++;
-     if( b1 > 192 )                    // byte comprimido?+     if (b1 > 192)                     // byte comprimido?
      {      {
         b2 = *src++;         b2 = *src++;
         i++;         i++;
-        for( j=0; j<(b1 & 63); j++ )   // sí, descomprime y escribe+        for (j=0; j<(b1 & 63); j++) {    // sí, descomprime y escribe
             *dst++ = b2;             *dst++ = b2;
 +        } 
      }      }
-     else+     else {
         *dst++ = b1;                   // no, es un byte de dato (escribe)         *dst++ = b1;                   // no, es un byte de dato (escribe)
 +     }  
   }   }
 } }
Línea 320: Línea 322:
  
  Nótese cómo:  Nótese cómo:
-\\+\\ 
  
   * Usamos los punteros src y dst para leer y escribir en memoria (operador de indirección ** * **).   * Usamos los punteros src y dst para leer y escribir en memoria (operador de indirección ** * **).
Línea 327: Línea 329:
   * Si está comprimido, no es un dato a guardar sino un dato RLE. Obtenemos el número de repeticiones reales (b1 en el ejemplo) y leemos el siguiente dato desde src. Ese dato (b2) tiene que ser escrito b1 veces.   * Si está comprimido, no es un dato a guardar sino un dato RLE. Obtenemos el número de repeticiones reales (b1 en el ejemplo) y leemos el siguiente dato desde src. Ese dato (b2) tiene que ser escrito b1 veces.
  
-\\+\\ 
  Ahora trasladamos esa rutina a ensamblador de Z80, y nos queda lo siguiente:  Ahora trasladamos esa rutina a ensamblador de Z80, y nos queda lo siguiente:
  
Línea 343: Línea 345:
 RLE_decompress: RLE_decompress:
  
-RLE_dec_loop+rle_dec_loop
-   LD A,(HL                         ; leemos un byte+    ld a, (hl                    ; leemos un byte
  
-   CP 192 +    cp 192 
-   JP NCRLE_dec_compressed          ; si byte > 192 = está comprimido +    jp ncrle_dec_compressed     ; si byte > 192 = está comprimido 
-   LD (DE), A                         ; si no está comprimido, escribirlo +    ld (de), a                    ; si no está comprimido, escribirlo 
-   INC DE +    inc de 
-   INC HL +    inc hl 
-   DEC BC+    dec bc
  
-RLE_dec_loop2+rle_dec_loop2
-   LD A,B +    ld ab 
-   OR C +    or c 
-   JR NZRLE_dec_loop +    jr nzrle_dec_loop 
-   RET                                 ; miramos si hemos acabado+    ret                           ; miramos si hemos acabado
  
-RLE_dec_compressed                   ; bucle para descompresión +rle_dec_compressed              ; bucle para descompresión 
-   PUSH BC +    push bc 
-   AND 63                              ; cogemos el numero de repeticiones +    and %00111111                 ; cogemos el numero de repeticiones 
-   LD BA                             ; lo salvamos en B +    ld ba                       ; lo salvamos en B 
-   INC HL                              ; y leemos otro byte (dato a repetir) +    inc hl                        ; y leemos otro byte (dato a repetir) 
-   LD A, (HL)+    ld a, (hl)
  
-RLE_dec_loop3+rle_dec_loop3
-   LD (DE),A                           ; bucle de escritura del dato B veces +    ld (de), a                    ; bucle de escritura del dato B veces 
-   INC DE +    inc de 
-   DJNZ RLE_dec_loop3 +    djnz rle_dec_loop3 
-   INC HL +    inc hl 
-   POP BC                              ; recuperamos BC +    pop bc                        ; recuperamos BC 
-   DEC BC                              ; Este DEC BC puede hacer BC=0 si los datos +    dec bc                        ; Este dec bc puede hacer BC=0 si los datos 
-                                       ; RLE no correctos. Cuidado (mem-smashing). +                                  ; RLE no son correctos. Cuidado (mem-smashing). 
-   DEC BC +    dec bc 
-   JR RLE_dec_loop2 +    jr rle_dec_loop2 
-   RET+    ret
 </code> </code>
  
- Finalmente, para aquellos que utilizan ASM de Z80 integrado en el compilador Z88DK, veamos la rutina descompresora integrada con el paso de parámetros de Z88DK: +\\ 
- +
-<code c> +
-int RLE_decompress_ASM( unsigned char *, unsigned char *, int ); +
- +
- +
-//--------------------------------------------------------------------------- +
-// RLE_decompress_ASM( src, dst, longitud ); +
-//--------------------------------------------------------------------------- +
-int RLE_decompress_ASM( unsigned char *src, unsigned char *dst, int length ) +
-+
- +
-#asm +
-   ld hl,2 +
-   add hl,sp +
- +
-   ld c, (hl) +
-   inc hl +
-   ld b, (hl) +
-   inc hl                              // BC = lenght +
- +
-   ld e, (hl) +
-   inc hl +
-   ld d, (hl) +
-   inc hl                              // de = dst +
-   push de +
- +
-   ld e, (hl) +
-   inc hl +
-   ld d, (hl) +
-   inc hl                              // de = src +
- +
-   ex de, hl +
-   pop de                              // now de = dst and hl = src +
- +
-   // After this:  HL = source, DE = destination, BC = lenght of RLE data +
- +
-.RLE_dec_loop +
-   ld a,(hl)                          // leemos un byte +
- +
-   cp 192 +
-   jp nc, RLE_dec_compressed          // si byte > 192 = está comprimido +
-   ld (de), a                         // si no está comprimido, escribirlo +
-   inc de +
-   inc hl +
-   dec bc +
- +
-.RLE_dec_loop2 +
-   ld a,b +
-   or c +
-   jr nz, RLE_dec_loop +
-   ret                                 // miramos si hemos acabado +
- +
-.RLE_dec_compressed                    // bucle para descompresión +
-   push bc +
-   and 63                              // cogemos el numero de repeticiones +
-   ld b, a                             // lo salvamos en B +
-   inc hl                              // y leemos otro byte (dato a repetir) +
-   ld a, (hl) +
- +
-.RLE_dec_loop3 +
-   ld (de),                          // bucle de escritura del dato B veces +
-   inc de +
-   djnz RLE_dec_loop3 +
-   inc hl +
-   pop bc                              // recuperamos BC +
-   dec bc                              // Este DEC BC puede hacer BC=0 si los datos +
-                                       // RLE no correctos. Cuidado (mem-smashing). +
-   dec bc +
-   jr RLE_dec_loop2 +
-   ret +
- +
-#endasm +
- +
-+
-</code> +
- +
- +
-\\+
  
 ===== Rutina compresora ===== ===== Rutina compresora =====
Línea 525: Línea 449:
                            char scanline_width, unsigned int length )                            char scanline_width, unsigned int length )
 { {
-  unsigned int offset, dst_pointer; +    unsigned int offset, dst_pointer; 
-  unsigned int bytecounter, width; +    unsigned int bytecounter, width; 
-  unsigned char b1, b2, data;+    unsigned char b1, b2, data;
  
-  dst_pointer = offset = 0; +    dst_pointer = offset = 0; 
-  bytecounter = 1; +    bytecounter = 1; 
-  width = 0;+    width = 0;
  
-  b1 = src[offset++];+    b1 = src[offset++];
  
-  do +    do 
-  +    
-     b2 = src[offset++]; +        b2 = src[offset++]; 
-     width++;+        width++;
  
-     while ((b2 == b1) && +        while ((b2 == b1) && 
-            (bytecounter < scanline_width-1 ) && +              (bytecounter < scanline_width-1 ) && 
-            (width < scanline_width-1)+              (width < scanline_width-1))
-     { +
-         bytecounter++; +
-         b2 = src[offset++]; +
-         width++; +
-     } +
-     if (width >= scanline_width) +
-     { +
-        offset += scanline_width-width; +
-        width = 0; +
-     } +
-     if (bytecounter != 1) +
-     { +
-        data = RLE_LIMIT+bytecounter; +
-        dst[dst_pointer++] = data; +
-        dst[dst_pointer++] = b1; +
-     } +
- +
-     if (bytecounter == 1) +
-     { +
-        if (b1 < RLE_LIMIT)+
         {         {
-          dst[dst_pointer++] = b1;+            bytecounter++; 
 +            b2 = src[offset++]
 +            width++;
         }         }
-        else+        if (width >= scanline_width)
         {         {
-          data = RLE_LIMIT+1+            offset += scanline_width-width; 
-          dst[dst_pointer++] = data; +            width = 0; 
-          dst[dst_pointer++] = b1;+        } 
 +        if (bytecounter != 1) 
 +        { 
 +            data = RLE_LIMIT+bytecounter
 +            dst[dst_pointer++] = data; 
 +            dst[dst_pointer++] = b1;
         }         }
-     } 
  
-     bytecounter = 1; +        if (bytecounter == 1) 
-     b1 = b2; +        { 
-  +            if (b1 < RLE_LIMIT) 
-  while (offset <= length);+            { 
 +                dst[dst_pointer++] = b1; 
 +            } 
 +            else 
 +            { 
 +                data = RLE_LIMIT+1; 
 +                dst[dst_pointer++] = data; 
 +                dst[dst_pointer++] = b1; 
 +            } 
 +        } 
 + 
 +        bytecounter = 1; 
 +        b1 = b2; 
 +    
 +    while (offset <= length);
  
-  return(dst_pointer);+    return(dst_pointer);
 } }
 </code> </code>
Línea 588: Línea 512:
  Lo normal durante el desarrollo del juego será "descomprimir" esos datos, no comprimirlos.  Lo normal durante el desarrollo del juego será "descomprimir" esos datos, no comprimirlos.
  
- En la sección de //Ficheros y Enlaces// tenéis disponible el compresor y descompresor "**rlezx**" para Linux y Windows (con su código fuente). Con él se pueden comprimir y descomprimir bloques de datos para incorporarlos en nuestros programas.+ En la sección de //Ficheros y Enlaces// tenéis disponible el compresor y descompresor ''rlezx'' para Linux y Windows (con su código fuente). Con él se pueden comprimir y descomprimir bloques de datos para incorporarlos en nuestros programas.
  
  
-\\+\\ 
 ===== Ejemplo completo ===== ===== Ejemplo completo =====
  
Línea 600: Línea 524:
   * Hacer un programa descompresor del RLE obtenido directamente sobre videoram.   * Hacer un programa descompresor del RLE obtenido directamente sobre videoram.
  
-{{ :cursos:ensamblador:sokoban.gif?508 }}+{{ :cursos:ensamblador:zx0_sokoban.png?640 |Pantalla de carga de Sokoban desempaquetada en VRAM}}
 ;#; ;#;
 //Pantalla de carga del Sokoban// //Pantalla de carga del Sokoban//
 ;#; ;#;
-\\+\\ 
  
  Veamos los diferentes pasos del proceso:  Veamos los diferentes pasos del proceso:
  
-\\+\\ 
  
 ==== Compresión de la imagen ==== ==== Compresión de la imagen ====
Línea 636: Línea 560:
  Como vemos, el tamaño de la pantalla ha pasado de 6912 bytes a 2571, lo que supone un ratio de compresión del 63%. La imagen ha ocupado finalmente casi 1/3 del tamaño original. Un ahorro de este tipo puede suponer gran cantidad de espacio aprovechable a partir de las pantallas de presentación, introducción, menúes, game-over o finales de juegos.  Como vemos, el tamaño de la pantalla ha pasado de 6912 bytes a 2571, lo que supone un ratio de compresión del 63%. La imagen ha ocupado finalmente casi 1/3 del tamaño original. Un ahorro de este tipo puede suponer gran cantidad de espacio aprovechable a partir de las pantallas de presentación, introducción, menúes, game-over o finales de juegos.
  
-\\+\\ 
  
 ==== Inclusión de la imagen en nuestro programa ==== ==== Inclusión de la imagen en nuestro programa ====
Línea 642: Línea 566:
  Ahora que ya tenemos el fichero binario de datos RLE comprimidos (sokoban.rle), debemos incluirlo dentro de nuestro programa. Podemos hacerlo de 2 formas:  Ahora que ya tenemos el fichero binario de datos RLE comprimidos (sokoban.rle), debemos incluirlo dentro de nuestro programa. Podemos hacerlo de 2 formas:
  
-\\ +\\  
-  * **Método INCBIN**: Incluyendo el binario directamente en PASMO con la directiva "INCBIN", asociándole una etiqueta para poder hacer referencia a él:\\ <code>+  * **Método INCBIN**: Incluyendo el binario directamente en Pasmo con la directiva ''INCBIN'' asociándole una etiqueta para poder hacer referencia a él:\\ \\ <code>
 Datos_Comprimidos: Datos_Comprimidos:
    INCBIN "fichero.rle"    INCBIN "fichero.rle"
 </code>\\ En este caso es importante no colocar los datos binarios en una zona de memoria que vaya a ser ejecutada. Lo normal es ubicarlos al final del fichero, como en el ejemplo que veremos a continuación. </code>\\ En este caso es importante no colocar los datos binarios en una zona de memoria que vaya a ser ejecutada. Lo normal es ubicarlos al final del fichero, como en el ejemplo que veremos a continuación.
-\\+\\ 
  
-  * **Método BIN2CODE**: Convirtiendo los datos binarios a "texto" con una utilidad como BIN2C o BIN2CODE (las tenéis disponibles como descargas en la sección de ficheros). Estas utilidades para PC (Linux, MAC, DOS/Windows) toman un fichero binario y lo convierten a datos listos para incluir en nuestros programas:\\ <code>+  * **Método BIN2CODE**: Convirtiendo los datos binarios a "texto" con una utilidad como BIN2C o BIN2CODE (las tenéis disponibles como descargas en la sección de ficheros). Estas utilidades para PC (Linux, MAC, DOS/Windows) toman un fichero binario y lo convierten a datos listos para incluir en nuestros programas:\\ \\ <code>
 [sromero@compiler:~rlezx]$ bin2code sokoban.rle datos.asm a [sromero@compiler:~rlezx]$ bin2code sokoban.rle datos.asm a
 BIN2CODE v1.0             By NoP of Compiler SoftWare BIN2CODE v1.0             By NoP of Compiler SoftWare
Línea 657: Línea 581:
  
 [sromero@compiler:~rlezx]$ cat datos.asm [sromero@compiler:~rlezx]$ cat datos.asm
-;  File created with  BIN2CODE  v1.0  by  NOP of Compiler SoftWare+;  File created with  BIN2CODE  v1.0  by  nop of Compiler SoftWare
  
 BINDATASIZE   EQU   2571 BINDATASIZE   EQU   2571
  
 BINDATA LABEL BYTE BINDATA LABEL BYTE
-   DB   255,255,193,255,255,255,193,255,239,255,193,248,7,206,255,193 +    DB   255,255,193,255,255,255,193,255,239,255,193,248,7,206,255,193 
-   DB   255,204,255,128,3,193,248,43,193,248,62,0,7,215,255,193 +    DB   255,204,255,128,3,193,248,43,193,248,62,0,7,215,255,193 
-   DB   240,198,0,82,120,127,202,255,193,255,255,255,193,255,255,255 +    DB   240,198,0,82,120,127,202,255,193,255,255,255,193,255,255,255 
-   DB   193,255,239,255,193,224,3,206,255,193,255,203,255,193,254,+    DB   193,255,239,255,193,224,3,206,255,193,255,203,255,193,254,
-   DB   1,193,248,0,193,240,62,52,1,215,255,193,240,128,193,250 +    DB   1,193,248,0,193,240,62,52,1,215,255,193,240,128,193,250 
-   DB   0,127,193,255,0,42,72,63,202,255,193,255,255,255,193,255 +    DB   0,127,193,255,0,42,72,63,202,255,193,255,255,255,193,255 
-   (etc...)+    (etc...)
 </code> </code>
  
- Es decir: el método 1 hace que PASMO incluya el binario directamente dentro de nuestro programa, y el método 2 lo convierte a directivas de datos "DBpara que lo incluyamos con sentencias **INCLUDE** o copiándolas y pegandolas dentro del código.+ Es decir: el método 1 hace que Pasmo incluya el binario directamente dentro de nuestro programa, y el método 2 lo convierte a directivas de datos ''DB''/''DEFB'' para que lo incluyamos con sentencias ''INCLUDE'' o copiándolas y pegandolas dentro del código.
  
-\\+\\ 
  
 ==== Programa de ejemplo de descompresión ==== ==== Programa de ejemplo de descompresión ====
Línea 679: Línea 603:
  Finalmente, sólo tenemos que juntar todas las piezas (esqueleto de programa básico, rutina de descompresión, datos RLE comprimidos) y añadirle una espera de pulsación a tecla para hacer nuestro ejemplo completo.  Finalmente, sólo tenemos que juntar todas las piezas (esqueleto de programa básico, rutina de descompresión, datos RLE comprimidos) y añadirle una espera de pulsación a tecla para hacer nuestro ejemplo completo.
  
- El programa siguiente, //ejemplo_rle.asm//, toma la pantalla gráfica comprimida con RLE (sokoban.rle, de 2571 bytes), y llama a la rutina de descompresión RLE indicando como dirección de descompresión la ubicación de la VIDEORAM (16384), con lo que estamos desempaquetando nuestros datos comprimidos directamente sobre la pantalla. Tras eso, espera a la pulsación de una tecla y vuelve al BASIC.+ El programa siguiente, ''ejemplo_rle.asm'', toma la pantalla gráfica comprimida con RLE (''sokoban.rle'', de 2571 bytes), y llama a la rutina de descompresión RLE indicando como dirección de descompresión la ubicación de la VIDEORAM (16384), con lo que estamos desempaquetando nuestros datos comprimidos directamente sobre la pantalla. Tras eso, espera a la pulsación de una tecla y vuelve al BASIC.
  
 <code asm> <code asm>
 ; Prueba de descompresion RLE ; Prueba de descompresion RLE
 ; Desempaquetamos un SCR comprimido con RLE sobre la pantalla ; Desempaquetamos un SCR comprimido con RLE sobre la pantalla
-ORG 35000 +    ORG 35000
- +
-  ; Cargamos los datos y preparamos nuestra rutina +
-  LD HL, Pantalla_Comprimida +
-  LD DE, 16384 +
-  LD BC, 2571 +
-  CALL RLE_decompress+
  
 +    ; Cargamos los datos y preparamos nuestra rutina
 +    ld hl, Pantalla_Comprimida
 +    ld de, 16384
 +    ld bc, 2571
 +    call RLE_decompress
  
 Wait_For_Keys_Pressed:         ; Bucle para esperar pulsación de tecla Wait_For_Keys_Pressed:         ; Bucle para esperar pulsación de tecla
-  XOR A +    xor a 
-  IN A, (254+    in a, ($fe
-  OR 224 +    or %11100000 
-  INC A +    inc a 
-  JR Z, Wait_For_Keys_Pressed +    jr z, Wait_For_Keys_Pressed 
- +    ret                          ; Fin del programa
-  RET                          ; Fin del programa +
- +
- +
-;; +
-;; RLE_decompress +
-;; Descomprime un bloque de datos RLE de memoria a memoria. +
-;; +
-;; Entrada a la rutina: +
-;; +
-;; HL = dirección origen de los datos RLE. +
-;; DE = destino donde descomprimir los datos. +
-;; BC = tamaño de los datos comprimidos. +
-;; +
-RLE_decompress: +
- +
-RLE_dec_loop: +
-   LD A, (HL)                         ; leemos un byte +
- +
-   CP 192 +
-   JP NC, RLE_dec_compressed          ; si byte > 192 = está comprimido +
-   LD (DE), A                         ; si no está comprimido, escribirlo +
-   INC DE +
-   INC HL +
-   DEC BC +
- +
-RLE_dec_loop2: +
-   LD A,B +
-   OR C +
-   JR NZ, RLE_dec_loop +
-   RET                                 ; miramos si hemos acabado +
- +
-RLE_dec_compressed:                    ; bucle para descompresión +
-   PUSH BC +
-   AND 63                              ; cogemos el numero de repeticiones +
-   LD B, A                             ; lo salvamos en B +
-   INC HL                              ; y leemos otro byte (dato a repetir) +
-   LD A, (HL) +
- +
-RLE_dec_loop3: +
-   LD (DE),                          ; bucle de escritura del dato B veces +
-   INC DE +
-   DJNZ RLE_dec_loop3 +
-   INC HL +
-   POP BC                              ; recuperamos BC +
-   DEC BC                              ; Este DEC BC puede hacer BC=0 si los datos +
-                                       ; RLE no correctos. Cuidado (mem-smashing). +
-   DEC BC +
-   JR RLE_dec_loop2 +
-   RET+
  
 +; Aquí incluiremos el código de la rutina RLE_decompress
 +;; --- RLE_decompress ---
  
 ; Aquí viene nuestra pantalla comprimida con RLE. ; Aquí viene nuestra pantalla comprimida con RLE.
 ; Hay que darse cuenta de que está fuera de todo ; Hay que darse cuenta de que está fuera de todo
-; código ejecutable, es decir, el RET de la rutina +; código ejecutable, es decir, el ret de la rutina 
-; principal y el RET de las subrutina de RLE_Decompress+; principal y el ret de las subrutina de RLE_Decompress
 ; hacen que nunca se llegue a este punto para ejecución. ; hacen que nunca se llegue a este punto para ejecución.
  
 Pantalla_Comprimida: Pantalla_Comprimida:
-  INCBIN sokoban.rle+    INCBIN sokoban.rle
  
-END 35000+    END 35000
 </code> </code>
  
-\\+\\ 
  
- Ensamblamos el programa con **pasmo <nowiki>--</nowiki>tapbas ejemplo_rle.asm ejemplo_rle.tap** y veremos que, al ejecutarlo, aparece la pantalla de carga de Sokoban, que no es más que el bloque de datos RLE descomprimidos directamente sobre la dirección de memoria en que empieza la videoram.+ Ensamblamos el programa con ''pasmo <nowiki>--</nowiki>tapbas ejemplo_rle.asm ejemplo_rle.tap'' y veremos que, al ejecutarlo, aparece la pantalla de carga de Sokoban, que no es más que el bloque de datos RLE descomprimidos directamente sobre la dirección de memoria en que empieza la videoram.
  
  
-\\+\\ 
  
 ===== Rutina optimizada por Z80user ===== ===== Rutina optimizada por Z80user =====
  
- El usuario Z80user, en los foros oficiales de Speccy.org, nos aporta una modificación de nuestra rutina original y una rutina compresora nativa:+ El usuario Z80user,  en los foros oficiales de Speccy.org,  nos aporta una modificación de nuestra rutina original y una rutina compresora nativa:
  
-\\ +\\  
-//He leído el articulo del RLE, he reescrito el codigo de la rutina descompresora y he creado la rutina compresora. He realizado la compresion de la rom de 48K, y posterior descompresión y salen identicas. He utilizado un pequeño truquito con el LDI y el RET PO (un flash no muy usado, pero que usa LDI para indicar BC=#0000) y me he ahorrado algunos bytes (compresora 62 bytes, descompresora 26 bytes).// +//He leído el articulo del RLE,  he reescrito el codigo de la rutina descompresora y he creado la rutina compresora. He realizado la compresion de la ROM de 48K,  y posterior descompresión y salen identicas. He utilizado un pequeño truquito con el ''LDI'' y el ''ret pO'' (un flash no muy usado,  pero que usa ''LDI'' para indicar BC=#0000) y me he ahorrado algunos bytes (compresora 62 bytes,  descompresora 26 bytes).// 
-\\+\\ 
  
 <code asm> <code asm>
Línea 788: Línea 664:
 ;; BC = tamaño de los datos a comprimir. ;; BC = tamaño de los datos a comprimir.
 ;; Salida ;; Salida
-;; AF,DE   desconocido+;; AF, DE   desconocido
 ;; HL = HL+longitud de los datos comprimidos ;; HL = HL+longitud de los datos comprimidos
 ;; IX = IX+BC ;; IX = IX+BC
Línea 797: Línea 673:
 ;; BC = tamaño de los datos a comprimir. ;; BC = tamaño de los datos a comprimir.
 ;; Salida ;; Salida
-;; AF,DE   desconocido+;; AF, DE   desconocido
 ;; HL = HL+longitud de los datos descomprimidos ;; HL = HL+longitud de los datos descomprimidos
 ;; DE = DE+BC ;; DE = DE+BC
Línea 803: Línea 679:
 //------------- //-------------
  
-RLE_descompress +RLE_descompress: 
-RLE_dec_loop +rle_dec_loop: 
-          LD   A,[HL]         ; Leemos 1 byte +          ld a(hl)                     ; Leemos 1 byte 
-          CP   A,192 +          cp a, 192 
-          JR   NC,RLE_dec     ; si byte > 192 = está comprimido +          jr ncrle_dec                 ; si byte > 192 = está comprimido 
-test_end  LDI                 ; Copiamos 1 byte en crudo +rle_test_end: 
-          RET   PO            ; Volvemos si hemos terminado +          ldi                            ; Copiamos 1 byte en crudo 
-          JR   RLE_dec_loop   ; Repetimos el bucle +          ret pO                         ; Volvemos si hemos terminado 
-RLE_dec                          ; bucle para descompresión RLE +          jr rle_dec_loop                ; Repetimos el bucle 
-          INC   HL            ; Nos colocamos en el valor +rle_dec:                                 ; bucle para descompresión RLE 
-          AND   A,#3F +          inc hl                         ; Nos colocamos en el valor 
-          JR   Z,test_end     ; Si 192, es dato en crudo +          and a$3f 
-          PUSH   BC +          jr zrle_test_end             ; Si 192,  es dato en crudo 
-          LD   B,A             ; B= numero de repeticiones +          push bc 
-          LD   A,[HL] +          ld ba                        ; B= numero de repeticiones 
-bucle     LD   [DE],A         ; \ +          ld a(hl) 
-          INC   DE             ;  Bucle de escritura B veces +rle_bucle: 
-          DJNZ   bucle        ; / +          ld (de)a                     ; \ 
-          POP   BC +          inc de                         ;  Bucle de escritura B veces 
-          DEC   BC             ; Ajustamos el contador al usar RLE +          djnz rle_bucle                 ; / 
-          JR   test_end        ; Copiamos 1 byte mas+          pop bc 
 +          dec bc                         ; Ajustamos el contador al usar RLE 
 +          jr rle_test_end                ; Copiamos 1 byte mas
  
  
 //--------------- //---------------
-RLE_Comprimir +RLE_Comprimir: 
-byte_1 +rle_byte_1: 
-          LD   E,[IX+#00]        ; leer byte +          ld e(ix+$00)                 ; leer byte 
-          INC   IX                ; incrementar posicion +          inc ix                         ; incrementar posicion 
-          DEC   BC                ; descontar contador +          dec bc                         ; descontar contador 
-          LD   A,E                ; +          ld ae 
-byte_2 +rle_byte_2: 
-          CP   A,#C0             ; Si es un codigo RLE +          cp a, #C0                      ; Si es un codigo RLE 
-          JR   NC,RLE_compress   ;  tratar como RLE +          jr ncrle_compress            ;  tratar como RLE 
-          CALL   get_byte        ; tomar el 2º byte +          call rle_get_byte              ; tomar el 2º byte 
-          JR   Z,ultimo_byte     ; falta escribir el ultimo byte +          jr zrle_ultimo_byte          ; falta escribir el ultimo byte 
-          CP   A,E                ; +          cp a, E 
-          JR   Z,RLE_compress2   ; usar compresion RLE si son identicos +          jr zrle_compress2            ; usar compresion RLE si son identicos 
-          LD   [HL],E             ; son distintos, escribir el byte anterior +          ld (hl)e                     ; son distintos,  escribir el byte anterior 
-          INC   HL                ; +          inc hl 
-          LD   E,A                ; recuperar el ultimo byte leido +          ld ea                        ; recuperar el ultimo byte leido 
-          JR   byte_2             ; continuar con la compresion +          jr byte_2                      ; continuar con la compresion 
-ultimo_byte   LD   [HL],E        ; escribir el ultimo byte +rle_ultimo_byte: 
-          INC   HL                ; +          ld (hl)e                     ; escribir el ultimo byte 
-          RET         ; salir +          inc hl 
-RLE_compress2 +          ret                            ; salir 
-          LD   D,#C1              ; eran identicos, empezar, con 2 +rle_compress2: 
-          JR   RLE_Repetido +          ld d$c1                      ; eran identicos,  empezar,  con 2 
-RLE_compress +          jr rle_repetido 
-          LD   D,#C0              ; era un valor RLE original +rle_compress: 
-RLE_Repetido +          ld d$c0                      ; era un valor RLE original 
-          CALL   get_byte         ; Obtener otro byte +rle_repetido: 
-          JR   Z,RLE_distinto     ; Escribir el valor RLE si no hya mas bytes +          call get_byte                  ; Obtener otro byte 
-          CP   A,E                ; Comprobar si es identico +          jr zrle_distinto             ; Escribir el valor RLE si no hya mas bytes 
-          JR   NZ,RLE_distinto    ; Se encontro un byte distinto +          cp a, E                        ; Comprobar si es identico 
-          INC                    ; incrementar el contador de repeticiones +          jr nzrle_distinto            ; Se encontro un byte distinto 
-          JR   NZ,RLE_Repetido    ; Otro byte identico +          inc d                          ; incrementar el contador de repeticiones 
-          DEC                   ; Se acabo el contador de repeticiones +          jr nzrle_repetido            ; Otro byte identico 
-RLE_distinto +          dec d                          ; Se acabo el contador de repeticiones 
-          LD   [HL],D              ; \ +rle_distinto: 
-          INC   HL                 ;  \ escribir valor RLE +          ld (hl)d                     ; \ 
-byte_simple +          inc l                          ;  \  
-          LD   [HL],E              ;  / +rle_byte_simple:                         ;   / escribir valor RLE 
-          INC   HL                 ; / +          ld (hl)e                     ;  / 
-          LD   E,A                 ; Recuperar el ultimo byte distinto +          inc hl                         ; / 
-          JR   byte_2              ; seguir comprimiendo +          ld ea                        ; Recuperar el ultimo byte distinto 
-get_byte +          jr rle_byte_2                      ; seguir comprimiendo 
-          LD   A,B                 ; \ +rle_get_byte: 
-          OR   A,C                 ;  Comprobar si es el ultimo byte +          ld ab                        ; \ 
-          RET                    ; / +          or a, C                        ;  Comprobar si es el ultimo byte 
-          DEC   BC                 ; descontar contador +          ret z                          ; / 
-          LD   A,[IX+#00]          ; leer byte +          dec bc                         ; descontar contador 
-          INC   IX                 ; incrementar posicion +          ld a(ix+$00)                 ; leer byte 
-          RET+          inc ix                         ; incrementar posicion 
 +          ret
 </code> </code>
  
- Agradecemos a Z80user su aportación, y aunque advertimos a los lectores que no podemos certificar el código, lo listamos para quien le pueda resultar de interés. 
  
- +\\ 
-\\+
 ===== ZX0: el algoritmo de compresión definitivo ===== ===== ZX0: el algoritmo de compresión definitivo =====
  
Línea 908: Línea 785:
 Pasamos de 6912 bytes a sólo 1383 bytes, una reducción espectacular que nos produce un fichero sokoban.scr.zx0 listo para usar. Pasamos de 6912 bytes a sólo 1383 bytes, una reducción espectacular que nos produce un fichero sokoban.scr.zx0 listo para usar.
  
-Este fichero puede ser incluído después en nuestro programa con INCBIN o introducido en un TAP para ser cargado como un bloque de datos con "LOAD "" CODE".+Este fichero puede ser incluído después en nuestro programa con INCBIN o introducido en un TAP para ser cargado como un bloque de datos con ''LOAD "" CODE''.
  
 Después, en nuestro programa, sólo tenemos que utilizar una de las rutinas descompresoras disponibles para desempaquetar los datos comprimidos en la zona de memoria deseada. Después, en nuestro programa, sólo tenemos que utilizar una de las rutinas descompresoras disponibles para desempaquetar los datos comprimidos en la zona de memoria deseada.
Línea 928: Línea 805:
  
 <code z80> <code z80>
-    LD    HL, scr_datos_zx0    ; origen = datos comprimidos +    ld    hl, scr_datos_zx0    ; origen = datos comprimidos 
-    LD    DE, 16384            ; destino = pantalla +    ld    de, 16384            ; destino = pantalla 
-    CALL  decompress_zx0+    call  decompress_zx0
 </code> </code>
  
Línea 945: Línea 822:
  
 <code z80> <code z80>
-ORG 33500+    ORG 33500
  
-    LD HL, pantalla_comprimida   ; datos comprimidos +    ld hl, pantalla_comprimida   ; datos comprimidos 
-    LD DE, 16384                 ; destino compresion +    ld de, 16384                 ; destino compresion 
-    CALL dzx0_standard           ; descomprimir+    call dzx0_standard           ; descomprimir
  
 loop: loop:
-    JR loop                      ; bucle para no volver a BASIC+    jr loop                      ; bucle para no volver a BASIC
  
     INCLUDE "dzx0_standard.asm"     INCLUDE "dzx0_standard.asm"
Línea 959: Línea 836:
     INCBIN "pantalla.scr.zx0"     INCBIN "pantalla.scr.zx0"
  
-END 33500+    END 33500
 </code> </code>
  
Línea 974: Línea 851:
 \\  \\ 
  
-También tenemos disponibles en la web de ZX0 rutinas que descomprimen al revés en memoria, denominadas dzx0_standard_back y dzx0_turbo_back.+También tenemos disponibles en la web de ZX0 rutinas que descomprimen al revés en memoria, denominadas ''dzx0_standard_back'' ''dzx0_turbo_back''.
  
 La utilidad de las rutinas "back" es la de descomprimir los datos en una posición de memoria que solape con el área de datos que estamos descomprimiendo. Es decir, machacamos los datos comprimidos con los descomprimidos (con lo que evidentemente no podremos volver a utilizarlos) para escenarios donde tenemos poco espacio en memoria donde desempaquetar. La utilidad de las rutinas "back" es la de descomprimir los datos en una posición de memoria que solape con el área de datos que estamos descomprimiendo. Es decir, machacamos los datos comprimidos con los descomprimidos (con lo que evidentemente no podremos volver a utilizarlos) para escenarios donde tenemos poco espacio en memoria donde desempaquetar.
Línea 991: Línea 868:
 A día de hoy, ZX0 es una herramienta imprescindible para hacer programas y juegos de Spectrum, ya que los ratios de compresión son muy buenos y el uso de datos comprimidos (gráficos, textos, e incluso código) supone una enorme diferencia en tiempos de carga y cantidad de recursos que podemos poner en nuestros juegos. A día de hoy, ZX0 es una herramienta imprescindible para hacer programas y juegos de Spectrum, ya que los ratios de compresión son muy buenos y el uso de datos comprimidos (gráficos, textos, e incluso código) supone una enorme diferencia en tiempos de carga y cantidad de recursos que podemos poner en nuestros juegos.
  
- +\\ 
- +
-\\+
 ===== Ficheros ===== ===== Ficheros =====
  
Línea 1006: Línea 881:
   * {{:cursos:ensamblador:zx0_test.zip|Ficheros de prueba del descompresor ZX0 (con Sokoban.scr)}}.   * {{:cursos:ensamblador:zx0_test.zip|Ficheros de prueba del descompresor ZX0 (con Sokoban.scr)}}.
  
- +\\ 
-\\+
 ===== En resumen ===== ===== En resumen =====
  
  Hemos visto los fundamentos de la compresión y descompresión RLE, así como rutinas C y ASM para implementarlos en nuestros programas. Mediante lo visto en este capítulo, podemos obtener un gran ahorro de memoria en nuestros programas, pudiendo introducir más cantidad de gráficos en el mismo espacio. También permite que pueda caber más código, más sonido o más texto, ya que aunque no apliquemos la compresión sobre este tipo de datos, podremos aprovechar el espacio que dejen libre nuestros gráficos comprimidos.  Hemos visto los fundamentos de la compresión y descompresión RLE, así como rutinas C y ASM para implementarlos en nuestros programas. Mediante lo visto en este capítulo, podemos obtener un gran ahorro de memoria en nuestros programas, pudiendo introducir más cantidad de gráficos en el mismo espacio. También permite que pueda caber más código, más sonido o más texto, ya que aunque no apliquemos la compresión sobre este tipo de datos, podremos aprovechar el espacio que dejen libre nuestros gráficos comprimidos.
- 
  
 \\  \\ 
 **[ [[.:indice|⬉]] | [[.:gfx5_mapeados|⬅]] | [[.:avanzadas1|➡]] ]** **[ [[.:indice|⬉]] | [[.:gfx5_mapeados|⬅]] | [[.:avanzadas1|➡]] ]**
 +
  • cursos/ensamblador/compresion_rle.1704559523.txt.gz
  • Última modificación: 06-01-2024 16:45
  • por sromero