Jyrki Alakuijala, Ph.D., Google, Inc., 2023-03-09
Resumen
WebP sin pérdida es un formato de imagen para la compresión sin pérdida de imágenes ARGB. El formato sin pérdida almacena y restablece los valores de píxeles exactamente, incluidos los valores de color de los píxeles completamente transparentes. Se usa un algoritmo universal para la compresión de datos secuenciales (LZ77), la codificación de prefijos y una caché de color para la compresión de los datos masivos. Se demostraron velocidades de decodificación más rápidas que las de PNG, así como una compresión un 25% más densa que la que se puede lograr con el formato PNG actual.
1 Introducción
En este documento, se describe la representación de datos comprimidos de una imagen WebP sin pérdida. Se diseñó como una referencia detallada para la implementación del codificador y decodificador sin pérdida de WebP.
En este documento, usamos ampliamente la sintaxis del lenguaje de programación C para describir el flujo de bits y suponemos la existencia de una función para leer bits, ReadBits(n). Los bytes se leen en el orden natural del flujo que los contiene, y los bits de cada byte se leen en orden de bit menos significativo primero. Cuando se leen varios bits al mismo tiempo, el número entero se construye a partir de los datos originales en el orden original. Los bits más significativos del número entero devuelto también son los bits más significativos de los datos originales. Por lo tanto, la instrucción
b = ReadBits(2);
es equivalente a las dos declaraciones siguientes:
b = ReadBits(1);
b |= ReadBits(1) << 1;
Suponemos que cada componente de color, es decir, alfa, rojo, azul y verde, se representa con un byte de 8 bits. Definimos el tipo correspondiente como uint8. Un píxel ARGB completo se representa con un tipo llamado uint32, que es un número entero sin signo que consta de 32 bits. En el código que muestra el comportamiento de las transformaciones, estos valores se codifican en los siguientes bits: alfa en los bits 31 a 24, rojo en los bits 23 a 16, verde en los bits 15 a 8 y azul en los bits 7 a 0. Sin embargo, las implementaciones del formato pueden usar otra representación de forma interna.
En términos generales, una imagen WebP sin pérdida contiene datos de encabezado, información de transformación y datos de imagen reales. Los encabezados contienen el ancho y la altura de la imagen. Una imagen WebP sin pérdida puede pasar por cuatro tipos diferentes de transformaciones antes de codificarse con entropía. La información de transformación en el flujo de bits contiene los datos necesarios para aplicar las transformaciones inversas respectivas.
2 Nomenclatura
- ARGB
- Es un valor de píxel que consta de valores alfa, rojo, verde y azul.
- Imagen ARGB
- Es un array bidimensional que contiene píxeles ARGB.
- Caché de color
- Es un pequeño array direccionado por hash para almacenar los colores usados recientemente y poder recuperarlos con códigos más cortos.
- Imagen de indexación de color
- Imagen unidimensional de colores que se puede indexar con un número entero pequeño (hasta 256 en WebP sin pérdidas).
- Imagen de transformación de color
- Es una imagen de subresolución bidimensional que contiene datos sobre las correlaciones de los componentes de color.
- Asignación de distancia
- Cambia las distancias LZ77 para que tengan los valores más pequeños para los píxeles en proximidad bidimensional.
- imagen de entropía
- Imagen bidimensional de resolución inferior que indica qué codificación de entropía se debe usar en un cuadrado respectivo de la imagen, es decir, cada píxel es un código de prefijo meta.
- LZ77
- Es un algoritmo de compresión de ventana deslizante basado en un diccionario que emite símbolos o los describe como secuencias de símbolos anteriores.
- código de prefijo de metadatos
- Es un número entero pequeño (hasta 16 bits) que indexa un elemento en la tabla de prefijos de metadatos.
- imagen del predictor
- Es una imagen bidimensional de resolución inferior que indica qué predictor espacial se usa para un cuadrado en particular de la imagen.
- código de prefijo
- Es una forma clásica de realizar la codificación de entropía en la que se usa una menor cantidad de bits para los códigos más frecuentes.
- codificación de prefijos
- Una forma de codificar con entropía números enteros más grandes, que codifica algunos bits del número entero con un código de entropía y codifica los bits restantes sin procesar. Esto permite que las descripciones de los códigos de entropía sigan siendo relativamente pequeñas, incluso cuando el rango de símbolos es grande.
- orden de línea de exploración
- Un orden de procesamiento de píxeles (de izquierda a derecha y de arriba hacia abajo), comenzando por el píxel superior izquierdo. Una vez que se completa una fila, continúa desde la columna de la izquierda de la siguiente fila.
3. Encabezado RIFF
El comienzo del encabezado tiene el contenedor RIFF. Consta de los siguientes 21 bytes:
- Cadena "RIFF".
- Es un valor de 32 bits en formato little-endian de la longitud del fragmento, que es el tamaño total del fragmento controlado por el encabezado RIFF. Por lo general, esto equivale al tamaño de la carga útil (tamaño del archivo menos 8 bytes: 4 bytes para el identificador "RIFF" y 4 bytes para almacenar el valor en sí).
- Cadena "WEBP" (nombre del contenedor RIFF).
- Cadena "VP8L" (FourCC para datos de imagen codificados sin pérdida).
- Es un valor de 32 bits en formato little-endian que indica la cantidad de bytes en el flujo sin pérdida.
- Firma de 1 byte 0x2f.
Los primeros 28 bits del flujo de bits especifican el ancho y la altura de la imagen. El ancho y la altura se decodifican como números enteros de 14 bits de la siguiente manera:
int image_width = ReadBits(14) + 1;
int image_height = ReadBits(14) + 1;
La precisión de 14 bits para el ancho y el alto de la imagen limita el tamaño máximo de una imagen WebP sin pérdida a 16384 × 16384 píxeles.
El bit alpha_is_used es solo una sugerencia y no debería afectar la decodificación. Debe establecerse en 0 cuando todos los valores alfa sean 255 en la imagen y en 1 en cualquier otro caso.
int alpha_is_used = ReadBits(1);
El version_number es un código de 3 bits que debe establecerse en 0. Cualquier otro valor se debe tratar como un error.
int version_number = ReadBits(3);
4 transformaciones
Las transformaciones son manipulaciones reversibles de los datos de la imagen que pueden reducir la entropía simbólica restante modelando las correlaciones espaciales y de color. Pueden hacer que la compresión final sea más densa.
Una imagen puede pasar por cuatro tipos de transformaciones. Un bit 1 indica la presencia de una transformación. Cada transformación solo se puede usar una vez. Las transformaciones solo se usan para la imagen ARGB de nivel principal; las imágenes de resolución secundaria (imagen de transformación de color, imagen de entropía y imagen de predictor) no tienen transformaciones, ni siquiera el bit 0 que indica el final de las transformaciones.
Por lo general, un codificador usaría estas transformaciones para reducir la entropía de Shannon en la imagen residual. Además, los datos de transformación se pueden decidir en función de la minimización de la entropía.
while (ReadBits(1)) { // Transform present.
// Decode transform type.
enum TransformType transform_type = ReadBits(2);
// Decode transform data.
...
}
// Decode actual image data (Section 5).
Si hay una transformación, los dos bits siguientes especifican el tipo de transformación. Existen cuatro tipos de transformaciones.
enum TransformType {
PREDICTOR_TRANSFORM = 0,
COLOR_TRANSFORM = 1,
SUBTRACT_GREEN_TRANSFORM = 2,
COLOR_INDEXING_TRANSFORM = 3,
};
El tipo de transformación está seguido de los datos de transformación. Los datos de transformación contienen la información necesaria para aplicar la transformación inversa y dependen del tipo de transformación. Las transformaciones inversas se aplican en el orden inverso en que se leen del flujo de bits, es decir, la última primero.
A continuación, describimos los datos de transformación para diferentes tipos.
4.1 Transformación del predictor
La transformación del predictor se puede usar para reducir la entropía aprovechando el hecho de que los píxeles vecinos suelen estar correlacionados. En la transformación del predictor, el valor actual del píxel se predice a partir de los píxeles ya decodificados (en orden de línea de exploración) y solo se codifica el valor residual (real menos el valor predicho). El componente verde de un píxel define cuál de los 14 predictores se usa dentro de un bloque específico de la imagen ARGB. El modo de predicción determina el tipo de predicción que se usará. Dividimos la imagen en cuadrados, y todos los píxeles de un cuadrado usan el mismo modo de predicción.
Los primeros 3 bits de los datos de predicción definen el ancho y el alto del bloque en cantidad de bits.
int size_bits = ReadBits(3) + 2;
int block_width = (1 << size_bits);
int block_height = (1 << size_bits);
#define DIV_ROUND_UP(num, den) (((num) + (den) - 1) / (den))
int transform_width = DIV_ROUND_UP(image_width, 1 << size_bits);
Los datos de transformación contienen el modo de predicción para cada bloque de la imagen. Es una imagen de subresolución en la que el componente verde de un píxel define cuál de los 14 predictores se usa para todos los píxeles block_width * block_height dentro de un bloque particular de la imagen ARGB. Esta imagen de resolución inferior se codifica con las mismas técnicas que se describen en el capítulo 5.
La cantidad de columnas de bloques, transform_width, se usa en la indexación bidimensional. Para un píxel (x, y), se puede calcular la dirección del bloque de filtro correspondiente de la siguiente manera:
int block_index = (y >> size_bits) * transform_width +
(x >> size_bits);
Hay 14 modos de predicción diferentes. En cada modo de predicción, el valor del píxel actual se predice a partir de uno o más píxeles vecinos cuyos valores ya se conocen.
Elegimos los píxeles adyacentes (TL, T, TR y L) del píxel actual (P) de la siguiente manera:
O O O O O O O O O O O
O O O O O O O O O O O
O O O O TL T TR O O O O
O O O O L P X X X X X
X X X X X X X X X X X
X X X X X X X X X X X
donde CI significa esquina superior izquierda, S significa superior, CD significa esquina superior derecha y I significa izquierda. En el momento de predecir un valor para P, ya se procesaron todos los píxeles de O, TL, T, TR y L, y se desconocen el píxel de P y todos los píxeles de X.
Dados los píxeles vecinos anteriores, los diferentes modos de predicción se definen de la siguiente manera.
| Modo | Valor previsto de cada canal del píxel actual |
|---|---|
| 0 | 0xff000000 (representa el color negro sólido en ARGB) |
| 1 | L |
| 2 | T |
| 3 | TR |
| 4 | TL |
| 5 | Average2(Average2(L, TR), T) |
| 6 | Average2(L, TL) |
| 7 | Average2(L, T) |
| 8 | Average2(TL, T) |
| 9 | Average2(T, TR) |
| 10 | Average2(Average2(L, TL), Average2(T, TR)) |
| 11 | Select(L, T, TL) |
| 12 | ClampAddSubtractFull(L, T, TL) |
| 13 | ClampAddSubtractHalf(Average2(L, T), TL) |
Average2 se define de la siguiente manera para cada componente ARGB:
uint8 Average2(uint8 a, uint8 b) {
return (a + b) / 2;
}
El predictor Select se define de la siguiente manera:
uint32 Select(uint32 L, uint32 T, uint32 TL) {
// L = left pixel, T = top pixel, TL = top-left pixel.
// ARGB component estimates for prediction.
int pAlpha = ALPHA(L) + ALPHA(T) - ALPHA(TL);
int pRed = RED(L) + RED(T) - RED(TL);
int pGreen = GREEN(L) + GREEN(T) - GREEN(TL);
int pBlue = BLUE(L) + BLUE(T) - BLUE(TL);
// Manhattan distances to estimates for left and top pixels.
int pL = abs(pAlpha - ALPHA(L)) + abs(pRed - RED(L)) +
abs(pGreen - GREEN(L)) + abs(pBlue - BLUE(L));
int pT = abs(pAlpha - ALPHA(T)) + abs(pRed - RED(T)) +
abs(pGreen - GREEN(T)) + abs(pBlue - BLUE(T));
// Return either left or top, the one closer to the prediction.
if (pL < pT) {
return L;
} else {
return T;
}
}
Las funciones ClampAddSubtractFull y ClampAddSubtractHalf se realizan para cada componente ARGB de la siguiente manera:
// Clamp the input value between 0 and 255.
int Clamp(int a) {
return (a < 0) ? 0 : (a > 255) ? 255 : a;
}
int ClampAddSubtractFull(int a, int b, int c) {
return Clamp(a + b - c);
}
int ClampAddSubtractHalf(int a, int b) {
return Clamp(a + (a - b) / 2);
}
Existen reglas especiales de tratamiento para algunos píxeles de borde. Si hay una transformación del predictor, independientemente del modo [0..13] para estos píxeles, el valor predicho para el píxel superior izquierdo de la imagen es 0xff000000, todos los píxeles de la fila superior son píxeles L y todos los píxeles de la columna más a la izquierda son píxeles T.
El píxel de TR para los píxeles de la columna más a la derecha es excepcional. Los píxeles de la columna más a la derecha se predicen con los modos [0..13], al igual que los píxeles que no están en el borde, pero el píxel más a la izquierda de la misma fila que el píxel actual se usa como píxel de TR.
El valor final del píxel se obtiene sumando cada canal del valor predicho al valor residual codificado.
void PredictorTransformOutput(uint32 residual, uint32 pred,
uint8* alpha, uint8* red,
uint8* green, uint8* blue) {
*alpha = ALPHA(residual) + ALPHA(pred);
*red = RED(residual) + RED(pred);
*green = GREEN(residual) + GREEN(pred);
*blue = BLUE(residual) + BLUE(pred);
}
4.2 Transformación de color
El objetivo de la transformación de color es descorrelacionar los valores R, G y B de cada píxel. La transformación de color mantiene el valor del verde (G) tal como está, transforma el valor del rojo (R) según el valor del verde y transforma el valor del azul (B) según el valor del verde y, luego, según el valor del rojo.
Al igual que en el caso de la transformación del predictor, primero la imagen se divide en bloques y se usa el mismo modo de transformación para todos los píxeles de un bloque. Para cada bloque, hay tres tipos de elementos de transformación de color.
typedef struct {
uint8 green_to_red;
uint8 green_to_blue;
uint8 red_to_blue;
} ColorTransformElement;
La transformación de color real se realiza definiendo un delta de transformación de color. El delta de transformación del color depende de ColorTransformElement, que es el mismo para todos los píxeles de un bloque en particular. El delta se resta durante la transformación de color. La transformación de color inversa consiste en agregar esos deltas.
La función de transformación de color se define de la siguiente manera:
void ColorTransform(uint8 red, uint8 blue, uint8 green,
ColorTransformElement *trans,
uint8 *new_red, uint8 *new_blue) {
// Transformed values of red and blue components
int tmp_red = red;
int tmp_blue = blue;
// Applying the transform is just subtracting the transform deltas
tmp_red -= ColorTransformDelta(trans->green_to_red, green);
tmp_blue -= ColorTransformDelta(trans->green_to_blue, green);
tmp_blue -= ColorTransformDelta(trans->red_to_blue, red);
*new_red = tmp_red & 0xff;
*new_blue = tmp_blue & 0xff;
}
ColorTransformDelta se calcula con un número entero de 8 bits con signo que representa un número de punto fijo de 3.5 y un canal de color RGB de 8 bits con signo (c) [-128..127] y se define de la siguiente manera:
int8 ColorTransformDelta(int8 t, int8 c) {
return (t * c) >> 5;
}
Se requiere una conversión de la representación sin firma de 8 bits (uint8) a la representación con firma de 8 bits (int8) antes de llamar a ColorTransformDelta(). El valor con signo se debe interpretar como un número de complemento a dos de 8 bits (es decir, el rango uint8 [128..255] se asigna al rango [-128..-1] de su valor int8 convertido).
La multiplicación se debe realizar con mayor precisión (con al menos 16 bits de precisión). La propiedad de extensión de signo de la operación de desplazamiento no importa aquí; solo se usan los 8 bits más bajos del resultado, y en estos bits, el desplazamiento de extensión de signo y el desplazamiento sin signo son coherentes entre sí.
Ahora, describimos el contenido de los datos de transformación de color para que la decodificación pueda aplicar la transformación de color inversa y recuperar los valores originales de rojo y azul. Los primeros 3 bits de los datos de transformación de color contienen el ancho y la altura del bloque de imagen en cantidad de bits, al igual que la transformación del predictor:
int size_bits = ReadBits(3) + 2;
int block_width = 1 << size_bits;
int block_height = 1 << size_bits;
La parte restante de los datos de transformación de color contiene instancias de ColorTransformElement, que corresponden a cada bloque de la imagen. Cada ColorTransformElement 'cte' se trata como un píxel en una imagen de resolución inferior cuyo componente alfa es 255, el componente rojo es cte.red_to_blue, el componente verde es cte.green_to_blue y el componente azul es cte.green_to_red.
Durante la decodificación, se decodifican las instancias ColorTransformElement de los bloques y se aplica la transformación de color inversa a los valores ARGB de los píxeles. Como se mencionó anteriormente, esa transformación de color inversa solo agrega valores de ColorTransformElement a los canales rojo y azul. Los canales alfa y verde se dejan tal como están.
void InverseTransform(uint8 red, uint8 green, uint8 blue,
ColorTransformElement *trans,
uint8 *new_red, uint8 *new_blue) {
// Transformed values of red and blue components
int tmp_red = red;
int tmp_blue = blue;
// Applying the inverse transform is just adding the
// color transform deltas
tmp_red += ColorTransformDelta(trans->green_to_red, green);
tmp_blue += ColorTransformDelta(trans->green_to_blue, green);
tmp_blue +=
ColorTransformDelta(trans->red_to_blue, tmp_red & 0xff);
*new_red = tmp_red & 0xff;
*new_blue = tmp_blue & 0xff;
}
4.3 Transformación Subtract Green
La transformación de resta de verde resta los valores de verde de los valores de rojo y azul de cada píxel. Cuando esta transformación está presente, el decodificador debe agregar el valor de verde a los valores de rojo y azul. No hay datos asociados con esta transformación. El decodificador aplica la transformación inversa de la siguiente manera:
void AddGreenToBlueAndRed(uint8 green, uint8 *red, uint8 *blue) {
*red = (*red + green) & 0xff;
*blue = (*blue + green) & 0xff;
}
Esta transformación es redundante, ya que se puede modelar con la transformación de color, pero, como no hay datos adicionales aquí, la transformación de resta de verde se puede codificar con menos bits que una transformación de color completa.
4.4 Transformación de indexación de color
Si no hay muchos valores de píxeles únicos, puede ser más eficiente crear un array de índice de color y reemplazar los valores de píxeles por los índices del array. La transformación de indexación de color logra esto. (En el contexto de WebP sin pérdida, no llamamos a esto específicamente una transformación de paleta porque existe un concepto similar pero más dinámico en la codificación sin pérdida de WebP: la caché de color).
La transformación de indexación de color verifica la cantidad de valores ARGB únicos en la imagen. Si ese número está por debajo de un umbral (256), se crea un array de esos valores ARGB, que luego se usa para reemplazar los valores de píxeles con el índice correspondiente: el canal verde de los píxeles se reemplaza con el índice, todos los valores alfa se establecen en 255 y todos los valores rojo y azul en 0.
Los datos de transformación contienen el tamaño de la tabla de colores y las entradas en la tabla de colores. El decodificador lee los datos de transformación de indexación de color de la siguiente manera:
// 8-bit value for the color table size
int color_table_size = ReadBits(8) + 1;
La tabla de colores se almacena con el mismo formato de almacenamiento de la imagen. La tabla de colores se puede obtener leyendo una imagen, sin el encabezado RIFF, el tamaño de la imagen ni las transformaciones, suponiendo una altura de 1 píxel y un ancho de color_table_size.
La tabla de colores siempre se codifica por sustracción para reducir la entropía de la imagen. Los deltas de los colores de la paleta suelen contener mucha menos entropía que los colores en sí, lo que genera ahorros significativos para las imágenes más pequeñas. En la decodificación, cada color final de la tabla de colores se puede obtener sumando los valores de los componentes de color anteriores por cada componente ARGB de forma independiente y almacenando los 8 bits menos significativos del resultado.
La transformación inversa de la imagen consiste simplemente en reemplazar los valores de píxel (que son índices de la tabla de colores) por los valores reales de la tabla de colores. La indexación se realiza en función del componente verde del color ARGB.
// Inverse transform
argb = color_table[GREEN(argb)];
Si el índice es igual o mayor que color_table_size, el valor de color argb debe establecerse en 0x00000000 (negro transparente).
Cuando la tabla de colores es pequeña (igual o inferior a 16 colores), varios píxeles se agrupan en un solo píxel. El agrupamiento de píxeles une varios píxeles (2, 4 u 8) en un solo píxel, lo que reduce el ancho de la imagen. El agrupamiento de píxeles permite una codificación de entropía de distribución conjunta más eficiente de los píxeles adyacentes y proporciona algunos beneficios similares a la codificación aritmética al código de entropía, pero solo se puede usar cuando hay 16 o menos valores únicos.
color_table_size especifica cuántos píxeles se combinan:
int width_bits;
if (color_table_size <= 2) {
width_bits = 3;
} else if (color_table_size <= 4) {
width_bits = 2;
} else if (color_table_size <= 16) {
width_bits = 1;
} else {
width_bits = 0;
}
width_bits tiene un valor de 0, 1, 2 o 3. Un valor de 0 indica que no se debe agrupar ningún píxel para la imagen. Un valor de 1 indica que se combinan dos píxeles y que cada píxel tiene un rango de [0..15]. Un valor de 2 indica que se combinan cuatro píxeles y que cada píxel tiene un rango de [0..3]. Un valor de 3 indica que se combinan ocho píxeles y que cada píxel tiene un rango de [0..1], es decir, un valor binario.
Los valores se empaquetan en el componente verde de la siguiente manera:
width_bits= 1: Para cada valor de x, donde x ≡ 0 (mod 2), se posiciona un valor de verde en x en los 4 bits menos significativos del valor de verde en x / 2, y se posiciona un valor de verde en x + 1 en los 4 bits más significativos del valor de verde en x / 2.width_bits= 2: Para cada valor de x, donde x ≡ 0 (mod 4), se posiciona un valor de verde en x en los 2 bits menos significativos del valor de verde en x / 4, y los valores de verde en x + 1 a x + 3 se posicionan en orden en los bits más significativos del valor de verde en x / 4.width_bits= 3: Para cada valor de x, donde x ≡ 0 (mod 8), se posiciona un valor de verde en x en el bit menos significativo del valor de verde en x / 8, y los valores de verde en x + 1 a x + 7 se posicionan en orden en los bits más significativos del valor de verde en x / 8.
Después de leer esta transformación, image_width se submuestrea con width_bits. Esto afecta el tamaño de las transformaciones posteriores. El nuevo tamaño se puede calcular con DIV_ROUND_UP, como se definió anteriormente.
image_width = DIV_ROUND_UP(image_width, 1 << width_bits);
5. Datos de imágenes
Los datos de la imagen son un array de valores de píxeles en orden de línea de exploración.
5.1 Roles de los datos de imágenes
Usamos datos de imágenes en cinco roles diferentes:
- Imagen ARGB: Almacena los píxeles reales de la imagen.
- Imagen de entropía: Almacena los códigos de prefijo de metadatos (consulta "Decodificación de códigos de prefijo de metadatos").
- Imagen del predictor: Almacena los metadatos de la transformación del predictor (consulta "Transformación del predictor").
- Imagen de transformación de color: Se crea con los valores de
ColorTransformElement(definidos en "Color Transform") para diferentes bloques de la imagen. - Imagen de indexación de color: Es un array del tamaño de
color_table_size(hasta 256 valores ARGB) que almacena los metadatos para la transformación de indexación de color (consulta "Transformación de indexación de color").
5.2 Codificación de datos de imágenes
La codificación de los datos de la imagen es independiente de su rol.
Primero, la imagen se divide en un conjunto de bloques de tamaño fijo (por lo general, bloques de 16 x 16). Cada uno de estos bloques se modela con sus propios códigos de entropía. Además, varios bloques pueden compartir los mismos códigos de entropía.
Justificación: Almacenar un código de entropía genera un costo. Este costo se puede minimizar si los bloques similares desde el punto de vista estadístico comparten un código de entropía, lo que permite almacenar ese código solo una vez. Por ejemplo, un codificador puede encontrar bloques similares agrupándolos en clústeres según sus propiedades estadísticas o uniendo repetidamente un par de clústeres seleccionados de forma aleatoria cuando se reduce la cantidad general de bits necesarios para codificar la imagen.
Cada píxel se codifica con uno de los tres métodos posibles:
- Literales codificados con prefijo: Cada canal (verde, rojo, azul y alfa) se codifica con entropía de forma independiente.
- Referencia hacia atrás LZ77: Se copia una secuencia de píxeles de otra parte de la imagen.
- Código de caché de color: Se usa un código hash multiplicativo corto (índice de caché de color) de un color visto recientemente.
En las siguientes subsecciones, se describe cada uno de estos elementos en detalle.
5.2.1 Literales codificados con prefijo
El píxel se almacena como valores codificados con prefijo de verde, rojo, azul y alfa (en ese orden). Consulta la Sección 6.2.3 para obtener más detalles.
5.2.2 Referencia hacia atrás de LZ77
Las referencias hacia atrás son tuplas de longitud y código de distancia:
- La longitud indica cuántos píxeles se deben copiar en orden de línea de exploración.
- El código de distancia es un número que indica la posición de un píxel visto anteriormente, desde el cual se copiarán los píxeles. La asignación exacta se describe a continuación.
Los valores de longitud y distancia se almacenan con la codificación de prefijo LZ77.
La codificación de prefijo LZ77 divide los valores enteros grandes en dos partes: el código de prefijo y los bits adicionales. El código de prefijo se almacena con un código de entropía, mientras que los bits adicionales se almacenan tal como están (sin un código de entropía).
Justificación: Este enfoque reduce el requisito de almacenamiento para el código de entropía. Además, los valores grandes suelen ser poco frecuentes, por lo que los bits adicionales se usarían para muy pocos valores en la imagen. Por lo tanto, este enfoque genera una mejor compresión en general.
En la siguiente tabla, se indican los códigos de prefijo y los bits adicionales que se usan para almacenar diferentes rangos de valores.
| Rango de valores | Código de prefijo | Brocas adicionales |
|---|---|---|
| 1 | 0 | 0 |
| 2 | 1 | 0 |
| 3 | 2 | 0 |
| 4 | 3 | 0 |
| 5..6 | 4 | 1 |
| 7..8 | 5 | 1 |
| 9..12 | 6 | 2 |
| 13..16 | 7 | 2 |
| … | … | … |
| 3072 a 4096 | 23 | 10 |
| … | … | … |
| 524289..786432 | 38 | 18 |
| 786433..1048576 | 39 | 18 |
El seudocódigo para obtener un valor (de longitud o distancia) del código de prefijo es el siguiente:
if (prefix_code < 4) {
return prefix_code + 1;
}
int extra_bits = (prefix_code - 2) >> 1;
int offset = (2 + (prefix_code & 1)) << extra_bits;
return offset + ReadBits(extra_bits) + 1;
Mapeo de distancia
Como se mencionó anteriormente, un código de distancia es un número que indica la posición de un píxel visto anteriormente, desde el cual se copiarán los píxeles. En esta subsección, se define la asignación entre un código de distancia y la posición de un píxel anterior.
Los códigos de distancia mayores que 120 denotan la distancia en píxeles en orden de línea de exploración, con un desplazamiento de 120.
Los códigos de distancia más pequeños [1..120] son especiales y se reservan para una vecindad cercana al píxel actual. Este vecindario consta de 120 píxeles:
- Píxeles que se encuentran de 1 a 7 filas por encima del píxel actual y hasta 8 columnas a la izquierda o hasta 7 columnas a la derecha del píxel actual. [La cantidad total de píxeles de este tipo es igual a
7 * (8 + 1 + 7) = 112]. - Píxeles que se encuentran en la misma fila que el píxel actual y hasta 8 columnas a la izquierda del píxel actual. [
8píxeles de ese tipo].
La asignación entre el código de distancia distance_code y el desplazamiento del píxel vecino (xi, yi) es la siguiente:
(0, 1), (1, 0), (1, 1), (-1, 1), (0, 2), (2, 0), (1, 2),
(-1, 2), (2, 1), (-2, 1), (2, 2), (-2, 2), (0, 3), (3, 0),
(1, 3), (-1, 3), (3, 1), (-3, 1), (2, 3), (-2, 3), (3, 2),
(-3, 2), (0, 4), (4, 0), (1, 4), (-1, 4), (4, 1), (-4, 1),
(3, 3), (-3, 3), (2, 4), (-2, 4), (4, 2), (-4, 2), (0, 5),
(3, 4), (-3, 4), (4, 3), (-4, 3), (5, 0), (1, 5), (-1, 5),
(5, 1), (-5, 1), (2, 5), (-2, 5), (5, 2), (-5, 2), (4, 4),
(-4, 4), (3, 5), (-3, 5), (5, 3), (-5, 3), (0, 6), (6, 0),
(1, 6), (-1, 6), (6, 1), (-6, 1), (2, 6), (-2, 6), (6, 2),
(-6, 2), (4, 5), (-4, 5), (5, 4), (-5, 4), (3, 6), (-3, 6),
(6, 3), (-6, 3), (0, 7), (7, 0), (1, 7), (-1, 7), (5, 5),
(-5, 5), (7, 1), (-7, 1), (4, 6), (-4, 6), (6, 4), (-6, 4),
(2, 7), (-2, 7), (7, 2), (-7, 2), (3, 7), (-3, 7), (7, 3),
(-7, 3), (5, 6), (-5, 6), (6, 5), (-6, 5), (8, 0), (4, 7),
(-4, 7), (7, 4), (-7, 4), (8, 1), (8, 2), (6, 6), (-6, 6),
(8, 3), (5, 7), (-5, 7), (7, 5), (-7, 5), (8, 4), (6, 7),
(-6, 7), (7, 6), (-7, 6), (8, 5), (7, 7), (-7, 7), (8, 6),
(8, 7)
Por ejemplo, el código de distancia 1 indica un desplazamiento de (0, 1) para el píxel vecino, es decir, el píxel que se encuentra arriba del píxel actual (0 píxeles de diferencia en la dirección X y 1 píxel de diferencia en la dirección Y).
Del mismo modo, el código de distancia 3 indica el píxel superior izquierdo.
El decodificador puede convertir un código de distancia distance_code en una distancia de orden de línea de exploración dist de la siguiente manera:
(xi, yi) = distance_map[distance_code - 1]
dist = xi + yi * image_width
if (dist < 1) {
dist = 1
}
donde distance_map es la asignación mencionada anteriormente y image_width es el ancho de la imagen en píxeles.
5.2.3 Codificación de caché de color
El caché de color almacena un conjunto de colores que se usaron recientemente en la imagen.
Justificación: De esta manera, a veces se puede hacer referencia a los colores usados recientemente de manera más eficiente que emitirlos con los otros dos métodos (descritos en 5.2.1 y 5.2.2).
Los códigos de caché de color se almacenan de la siguiente manera. Primero, hay un valor de 1 bit que indica si se usa la caché de color. Si este bit es 0, no existen códigos de caché de color y no se transmiten en el código de prefijo que decodifica los símbolos verdes y los códigos de prefijo de longitud. Sin embargo, si este bit es 1, a continuación, se lee el tamaño de la caché de color:
int color_cache_code_bits = ReadBits(4);
int color_cache_size = 1 << color_cache_code_bits;
color_cache_code_bits define el tamaño de la caché de color (1 <<
color_cache_code_bits). El rango de valores permitidos para color_cache_code_bits es [1..11]. Los decodificadores que cumplen con los requisitos deben indicar un flujo de bits dañado para otros valores.
Una caché de color es un array de tamaño color_cache_size. Cada entrada almacena un color ARGB. Los colores se buscan indexándolos por (0x1e35a7bd * color) >> (32 -
color_cache_code_bits). Solo se realiza una búsqueda en una caché de color; no hay resolución de conflictos.
Al comienzo de la decodificación o codificación de una imagen, todas las entradas de todos los valores de la caché de color se establecen en cero. El código de caché de color se convierte a este color en el momento de la decodificación. El estado de la caché de color se mantiene insertando cada píxel, ya sea producido por referencia hacia atrás o como literales, en la caché en el orden en que aparecen en el flujo.
6. Código de entropía
6.1 Descripción general
La mayoría de los datos se codifican con un código de prefijo canónico. Por lo tanto, los códigos se transmiten enviando las longitudes de los códigos de prefijo, en lugar de los códigos de prefijo reales.
En particular, el formato usa la codificación de prefijo espacialmente variante. En otras palabras, diferentes bloques de la imagen pueden usar diferentes códigos de entropía.
Justificación: Las diferentes áreas de la imagen pueden tener características diferentes. Por lo tanto, permitirles usar diferentes códigos de entropía proporciona más flexibilidad y, potencialmente, una mejor compresión.
6.2 Detalles
Los datos de la imagen codificada constan de varias partes:
- Decodificación y compilación de los códigos de prefijo
- Son los prefijos de los códigos de Meta.
- Son datos de imagen codificados con entropía.
Para cada píxel determinado (x, y), hay un conjunto de cinco códigos de prefijo asociados. Estos códigos son (en orden de flujo de bits):
- Prefijo de código 1: Se usa para el canal verde, la longitud de referencia hacia atrás y la caché de color.
- Prefijo de código 2, 3 y 4: Se usan para los canales rojo, azul y alfa, respectivamente.
- Prefijo de código núm. 5: Se usa para la distancia de referencia hacia atrás.
A partir de aquí, nos referiremos a este conjunto como un grupo de códigos de prefijo.
6.2.1 Decodificación y compilación de los códigos de prefijo
En esta sección, se describe cómo leer las longitudes de código de prefijo del flujo de bits.
Las longitudes de los códigos de prefijo se pueden codificar de dos maneras. El método utilizado se especifica con un valor de 1 bit.
- Si este bit es 1, se trata de un código de longitud simple.
- Si este bit es 0, se trata de un código de longitud normal.
En ambos casos, puede haber longitudes de código sin usar que sigan formando parte de la transmisión. Esto puede ser ineficiente, pero el formato lo permite. El árbol descrito debe ser un árbol binario completo. Un solo nodo hoja se considera un árbol binario completo y se puede codificar con el código de longitud simple o el código de longitud normal. Cuando se codifica un solo nodo hoja con el código de longitud normal, todas las longitudes de código, excepto una, son cero, y el valor del nodo hoja único se marca con la longitud 1, incluso cuando no se consumen bits cuando se usa ese árbol de nodo hoja único.
Código de longitud simple
Esta variante se usa en el caso especial en el que solo 1 o 2 símbolos de prefijo se encuentran en el rango [0..255] con una longitud de código 1. Todas las demás longitudes de códigos de prefijo son implícitamente cero.
El primer bit indica la cantidad de símbolos:
int num_symbols = ReadBits(1) + 1;
A continuación, se indican los valores de los símbolos.
Este primer símbolo se codifica con 1 u 8 bits, según el valor de is_first_8bits. El rango es [0..1] o [0..255], respectivamente. Si está presente, se supone que el segundo símbolo siempre está en el rango [0..255] y se codifica con 8 bits.
int is_first_8bits = ReadBits(1);
symbol0 = ReadBits(1 + 7 * is_first_8bits);
code_lengths[symbol0] = 1;
if (num_symbols == 2) {
symbol1 = ReadBits(8);
code_lengths[symbol1] = 1;
}
Los dos símbolos deben ser diferentes. Se permiten símbolos duplicados, pero son ineficientes.
Nota: Otro caso especial es cuando todas las longitudes de los códigos de prefijo son cero (un código de prefijo vacío). Por ejemplo, un código de prefijo para la distancia puede estar vacío si no hay referencias hacia atrás. Del mismo modo, los códigos de prefijo para alfa, rojo y azul pueden estar vacíos si todos los píxeles dentro del mismo código de prefijo de metadatos se producen con la caché de color. Sin embargo, este caso no necesita un tratamiento especial, ya que los códigos de prefijo vacíos se pueden codificar como los que contienen un solo símbolo 0.
Código de longitud normal
Las longitudes de código del código de prefijo caben en 8 bits y se leen de la siguiente manera.
Primero, num_code_lengths especifica la cantidad de longitudes de código.
int num_code_lengths = 4 + ReadBits(4);
Las longitudes de código se codifican con códigos de prefijo. Primero, se deben leer las longitudes de código de nivel inferior, code_length_code_lengths. El resto de esos code_length_code_lengths (según el orden en kCodeLengthCodeOrder) son ceros.
int kCodeLengthCodes = 19;
int kCodeLengthCodeOrder[kCodeLengthCodes] = {
17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
};
int code_length_code_lengths[kCodeLengthCodes] = { 0 }; // All zeros
for (i = 0; i < num_code_lengths; ++i) {
code_length_code_lengths[kCodeLengthCodeOrder[i]] = ReadBits(3);
}
A continuación, si ReadBits(1) == 0, la cantidad máxima de diferentes símbolos de lectura (max_symbol) para cada tipo de símbolo (A, R, G, B y distancia) se establece en el tamaño de su alfabeto:
- Canal G: 256 + 24 +
color_cache_size - Otros literales (A, R y B): 256
- Código de distancia: 40
De lo contrario, se define de la siguiente manera:
int length_nbits = 2 + 2 * ReadBits(3);
int max_symbol = 2 + ReadBits(length_nbits);
Si max_symbol es mayor que el tamaño del alfabeto para el tipo de símbolo, el flujo de bits no es válido.
Luego, se compila una tabla de prefijos a partir de code_length_code_lengths y se usa para leer hasta max_symbol longitudes de código.
- El código [0..15] indica longitudes de código literales.
- El valor 0 significa que no se codificaron símbolos.
- Los valores del 1 al 15 indican la longitud en bits del código respectivo.
- El código 16 repite el valor anterior distinto de cero entre 3 y 6 veces, es decir,
3 + ReadBits(2)veces. Si se usa el código 16 antes de que se emita un valor distinto de cero, se repite el valor 8. - El código 17 emite una secuencia de ceros de longitud [3..10], es decir,
3 + ReadBits(3)veces. - El código 18 emite una secuencia de ceros de longitud [11..138], es decir,
11 + ReadBits(7)veces.
Una vez que se leen las longitudes de los códigos, se forma un código de prefijo para cada tipo de símbolo (A, R, G, B y distancia) con sus respectivos tamaños de alfabeto.
El código de longitud de código normal debe codificar un árbol de decisión completo, es decir, la suma de 2 ^ (-length) para todos los códigos distintos de cero debe ser exactamente uno. Sin embargo, hay una excepción a esta regla: el árbol de un solo nodo hoja, en el que el valor del nodo hoja se marca con el valor 1 y los demás valores son 0.
6.2.2 Decodificación de códigos de prefijo de metadatos
Como se mencionó anteriormente, el formato permite el uso de diferentes códigos de prefijo para diferentes bloques de la imagen. Los códigos de prefijo de metadatos son índices que identifican qué códigos de prefijo se deben usar en diferentes partes de la imagen.
Los códigos de prefijo de metadatos solo se pueden usar cuando la imagen se usa en el rol de una imagen ARGB.
Existen dos posibilidades para los códigos de prefijo de metadatos, indicadas por un valor de 1 bit:
- Si este bit es cero, solo hay un código de prefijo de metadatos que se usa en toda la imagen. No se almacenan más datos.
- Si este bit es uno, la imagen usa varios códigos de prefijo de metadatos. Estos códigos de prefijo de metadatos se almacenan como una imagen de entropía (que se describe a continuación).
Los componentes rojo y verde de un píxel definen un código de prefijo de metadatos de 16 bits que se usa en un bloque específico de la imagen ARGB.
Imagen de entropía
La imagen de entropía define qué códigos de prefijo se usan en diferentes partes de la imagen.
Los primeros 3 bits contienen el valor de prefix_bits. Las dimensiones de la imagen de entropía se derivan de prefix_bits:
int prefix_bits = ReadBits(3) + 2;
int prefix_image_width =
DIV_ROUND_UP(image_width, 1 << prefix_bits);
int prefix_image_height =
DIV_ROUND_UP(image_height, 1 << prefix_bits);
donde DIV_ROUND_UP se define anteriormente.
Los siguientes bits contienen una imagen de entropía con un ancho de prefix_image_width y una altura de prefix_image_height.
Interpretación de los prefijos de Meta
La cantidad de grupos de códigos de prefijo en la imagen ARGB se puede obtener buscando el código de prefijo de metadatos más grande en la imagen de entropía:
int num_prefix_groups = max(entropy image) + 1;
Aquí, max(entropy image) indica el código de prefijo más grande almacenado en la imagen de entropía.
Como cada grupo de códigos de prefijo contiene cinco códigos de prefijo, la cantidad total de códigos de prefijo es la siguiente:
int num_prefix_codes = 5 * num_prefix_groups;
Dado un píxel (x, y) en la imagen ARGB, podemos obtener los códigos de prefijo correspondientes para usarlos de la siguiente manera:
int position =
(y >> prefix_bits) * prefix_image_width + (x >> prefix_bits);
int meta_prefix_code = (entropy_image[position] >> 8) & 0xffff;
PrefixCodeGroup prefix_group = prefix_code_groups[meta_prefix_code];
donde se supone la existencia de la estructura PrefixCodeGroup, que representa un conjunto de cinco códigos de prefijo. Además, prefix_code_groups es un array de PrefixCodeGroup (de tamaño num_prefix_groups).
Luego, el decodificador usa el grupo de códigos de prefijo prefix_group para decodificar el píxel (x, y), como se explica en "Decodificación de datos de imágenes codificados con entropía".
6.2.3 Decodificación de datos de imagen codificados con entropía
Para la posición actual (x, y) en la imagen, el decodificador primero identifica el grupo de códigos de prefijo correspondiente (como se explicó en la última sección). Dado el grupo de códigos de prefijo, el píxel se lee y decodifica de la siguiente manera.
A continuación, lee el símbolo S del flujo de bits con el código de prefijo núm. 1. Ten en cuenta que S es cualquier número entero en el rango de 0 a (256 + 24 + color_cache_size- 1).
La interpretación de S depende de su valor:
- Si S < 256
- Usa S como el componente verde.
- Lee el rojo del flujo de bits con el código de prefijo núm. 2.
- Lee el azul del flujo de bits con el código de prefijo núm. 3.
- Lee el valor de alfa del flujo de bits con el código de prefijo núm. 4.
- Si S >= 256 y S < 256 + 24
- Usa S-256 como código de prefijo de longitud.
- Lee bits adicionales para la longitud del flujo de bits.
- Determina la longitud de la referencia hacia atrás L a partir del código de prefijo de longitud y los bits adicionales leídos.
- Lee el código de prefijo de distancia del flujo de bits con el código de prefijo núm. 5.
- Lee bits adicionales para la distancia del flujo de bits.
- Determina la distancia de referencia hacia atrás D a partir del código de prefijo de distancia y los bits adicionales leídos.
- Copia píxeles L (en orden de línea de exploración) de la secuencia de píxeles que comienza en la posición actual menos D píxeles.
- Si S >= 256 + 24
- Usa S - (256 + 24) como índice en la caché de color.
- Obtiene el color ARGB de la caché de color en ese índice.
7. Estructura general del formato
A continuación, se muestra una vista del formato en Augmented Backus-Naur Form (ABNF) RFC 5234 RFC 7405. No abarca todos los detalles. El final de la imagen (EOI) solo se codifica de forma implícita en la cantidad de píxeles (image_width * image_height).
Ten en cuenta que *element significa que element se puede repetir 0 o más veces. 5element significa que element se repite exactamente 5 veces. %b representa un valor binario.
7.1 Estructura básica
format = RIFF-header image-header image-stream
RIFF-header = %s"RIFF" 4OCTET %s"WEBPVP8L" 4OCTET
image-header = %x2F image-size alpha-is-used version
image-size = 14BIT 14BIT ; width - 1, height - 1
alpha-is-used = 1BIT
version = 3BIT ; 0
image-stream = optional-transform spatially-coded-image
7.2 Estructura de las transformaciones
optional-transform = (%b1 transform optional-transform) / %b0
transform = predictor-tx / color-tx / subtract-green-tx
transform =/ color-indexing-tx
predictor-tx = %b00 predictor-image
predictor-image = 3BIT ; sub-pixel code
entropy-coded-image
color-tx = %b01 color-image
color-image = 3BIT ; sub-pixel code
entropy-coded-image
subtract-green-tx = %b10
color-indexing-tx = %b11 color-indexing-image
color-indexing-image = 8BIT ; color count
entropy-coded-image
7.3 Estructura de los datos de la imagen
spatially-coded-image = color-cache-info meta-prefix data
entropy-coded-image = color-cache-info data
color-cache-info = %b0
color-cache-info =/ (%b1 4BIT) ; 1 followed by color cache size
meta-prefix = %b0 / (%b1 entropy-image)
data = prefix-codes lz77-coded-image
entropy-image = 3BIT ; subsample value
entropy-coded-image
prefix-codes = prefix-code-group *prefix-codes
prefix-code-group =
5prefix-code ; See "Interpretation of Meta Prefix Codes" to
; understand what each of these five prefix
; codes are for.
prefix-code = simple-prefix-code / normal-prefix-code
simple-prefix-code = ; see "Simple Code Length Code" for details
normal-prefix-code = ; see "Normal Code Length Code" for details
lz77-coded-image =
*((argb-pixel / lz77-copy / color-cache-code) lz77-coded-image)
A continuación, se muestra una posible secuencia de ejemplo:
RIFF-header image-size %b1 subtract-green-tx
%b1 predictor-tx %b0 color-cache-info
%b0 prefix-codes lz77-coded-image