Un titular relleno con 1 0 0 rg, rojo puro, vuelve de una conversión a escala de grises como 0.299 g. El azul puro aterriza en 0.114 g, lo bastante cerca del negro como para que un logo azul marino y el texto del cuerpo cuesten de distinguir. Esos números no son un accidente de redondeo: son los pesos de luminancia BT.601, los mismos que aplica un visor cuando muestra una página RGB en un dispositivo de grises, y deciden cuánto contraste sobrevive cuando un folleto a color tiene que volverse gris
Qué calcula RecolorDocument para cada color
PDF Library for Delphi expone la conversión como RecolorDocument(TargetSpace) sobre TPDFlib, con 1 para gris, 2 para RGB y 3 para CMYK. Devuelve el número de cosas que convirtió, y la geometría, las posiciones de texto y la colocación de imágenes se quedan donde estaban. Solo cambian los valores de color y los nombres de espacio de color que los rodean
Todo el color pasa por RGB de camino al destino. El gris es la suma de luminancia 0.299 R + 0.587 G + 0.114 B. El CMYK llega a RGB por la regla de dispositivo llana de ISO 32000-1 §10.3, donde cada canal es uno menos la suma de su tinta y el negro, saturado a cero. El RGB llega a CMYK tirando del componente mayor hacia el negro: K = 1 - max(R, G, B), y las tintas restantes se escalan de lo que queda. Unos cuantos resultados que conviene conocer antes de enviar nada a impresión:
- Un gris de
0.4 gconvertido a CMYK se vuelve0 0 0 0.6 k, solo tinta negra, que es lo que una imprenta quiere para texto y filetes - El RGB
1 0.5 0(naranja) se vuelve0 0.5 1 0 k, sin nada de negro - Un negro enriquecido como
0.6 0.4 0.4 1 kse vuelve0 gde camino a gris, así que las tintas extra desaparecen sin más - El cian puro,
1 0 0 0 k, se convierte en0.701 g, más claro de lo que la mayoría espera de una tinta saturada
Los operandos convertidos se escriben con tres decimales como máximo y siempre con punto como separador decimal, sea cual sea el locale de la máquina que ejecuta el trabajo. ISO 32000-1 §7.3.3 no permite otra cosa en un content stream, y un conversor que formatee números con el locale del sistema escribirá 0,5 g en una estación de trabajo alemana, lo que un lector parsea como el número 0 seguido de un operador desconocido
var
Lib: TPDFlib;
Converted: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('brochure.pdf', '') <> 1 then
raise Exception.Create('Could not open brochure.pdf');
Converted := Lib.RecolorDocument(1); // 1 = gris, 2 = RGB, 3 = CMYK
WriteLn(Format('%d colors, images and gradients converted', [Converted]));
Lib.SaveToFile('brochure-gray.pdf');
finally
Lib.Free;
end;
end;
Una página cuyos colores ya están en el espacio destino conserva sus bytes de contenido originales. RecolorDocument cuenta solo las conversiones que de verdad cruzan de un espacio a otro, así que un documento que ya es gris informa 0 y nada suyo se reescribe. En un archivo grande eso importa: un diff de entrada y salida muestra exactamente las páginas que cambiaron
¿Por qué RecolorPage deja la foto de portada a color?
RecolorPage convierte los operadores de color de la página seleccionada y se detiene ahí. Las imágenes y los degradados son objetos a nivel de documento, a menudo compartidos por varias páginas, y solo RecolorDocument los convierte. Así que la forma obvia de convertir solo una portada, seleccionar la página 1 y llamar a RecolorPage(3), te da titulares y filetes en CMYK sobre una fotografía RGB
Ese reparto es deliberado. Un XObject de imagen dibujado en la portada puede estar dibujado también en la página 12, y convertirlo para una página lo convierte para ambas. Una llamada de alcance de página que alcanzara en silencio recursos compartidos cambiaría páginas que el llamador nunca seleccionó. Esa es la peor sorpresa de las dos, así que la llamada por página toca solo lo que pertenece a la página
// Operadores solo de la portada; imágenes y degradados no se convierten aquí
Lib.SelectPage(1);
Changed := Lib.RecolorPage(3);
if Changed = 0 then
WriteLn('Cover already uses CMYK operators (or the target was out of range)');
Lib.SaveToFile('cover-cmyk-operators.pdf');
// Para un documento que tiene que ser un solo espacio de punta a punta, conviértelo entero
Lib.RecolorDocument(3);
Lib.SaveToFile('all-cmyk.pdf');
Fíjate en el valor de retorno de ese fragmento. Ambas llamadas devuelven 0 para un destino fuera de 1 a 3, y 0 es también la respuesta para una página que no necesitaba cambio, así que una errata en el destino se ve exactamente igual que un documento que ya era correcto. Valida tú el argumento antes de la llamada. RecolorDocument restaura la página que estuviera seleccionada antes de ejecutarse, lo que significa que puedes llamarlo en medio de un trabajo página a página sin volver a seleccionar después
Un detalle de alcance más. El pase de operadores lee el content stream propio de cada página, las instrucciones de dibujo listadas en la página misma, y convierte g, rg, k, sus formas de trazo, y sc / scn cuando el espacio actual es un espacio de dispositivo. Cuando /DeviceRGB cs precede a un sc convertido, el nombre del espacio también se reescribe al destino. Sáltate ese renombrado en un conversor escrito a mano y dejas al lector buscando tres componentes RGB donde ahora hay un valor gris
Imágenes y paletas: cuáles cambian, cuáles conservan sus bytes
Las imágenes importan tanto como los operadores. Un folleto convertido a gris con solo sus operadores tratados sigue mostrando fotografías a color en páginas grises, lo que lee peor que el original o que una conversión completa. RecolorDocument convierte una imagen cuando tiene ocho bits por componente, un espacio de color de dispositivo, y o bien sin compresión o bien Flate llano sin parámetros decode
Las imágenes indexadas toman otra ruta. Sus muestras son índices dentro de una tabla de color, así que la tabla se convierte y cada muestra conserva el índice que ya tenía. Una captura de pantalla o un gráfico guardado como imagen de paleta de 256 colores se convierte por esta vía sin importar cómo estén empaquetadas o comprimidas sus muestras, porque las muestras jamás se decodifican. La base de la paleta tiene que ser un espacio de dispositivo; una paleta sobre un perfil ICC conserva su tabla
Una imagen JPEG o con codificación fax conserva sus bytes. Cambiar sus colores significaría decodificarla, convertirla y recomprimirla, lo que añade una segunda generación de artefactos JPEG y cambia el tamaño del archivo de maneras que nadie pidió. Si un trabajo necesita de verdad cada foto en gris, la recodificación es una decisión aparte con su propio ajuste de calidad, y preferimos que el llamador la tome explícitamente antes que una conversión de color la tome por él
Las máscaras suaves y las de plantilla se quedan como están por un motivo distinto. Una máscara suave suele ser una imagen DeviceGray, pero sus valores significan cobertura. Convierte una a RGB de tres componentes y un visor deja de tratarla como transparencia; acaba pintada como ilustración gris encima del contenido que debía moldear. RecolorDocument recoge cada objeto referenciado por /SMask o /Mask antes de convertir cualquier imagen, y una imagen con /ImageMask true no tiene color que convertir para empezar
Los colores establecidos mediante espacios ICCBased, Separation, Indexed o Pattern a nivel de operador también se dejan tal cual están escritos. Convertir un color directo o un valor atado a un perfil a gris de dispositivo sin leer su perfil cambiaría el aspecto de la página, y un pase cuyo trabajo es conservar la apariencia no puede ponerse a adivinar qué significaba un perfil
Gradientes: el espacio de color y las paradas se mueven juntos
Un shading declara su espacio de color una vez y entrega cada parada a una función. Renombra /ColorSpace de DeviceRGB a DeviceGray mientras la función sigue devolviendo tres números, y un lector toma el primero de ellos, el canal rojo, como nivel de gris. Un degradado de azul a blanco renderiza entonces como una banda que empieza en negro. RecolorDocument recorre primero todo el árbol de funciones en solo lectura y no convierte nada salvo que cada nodo suyo sea convertible; solo entonces reescribe las paradas y renombra el espacio
Las funciones que reescribe son del tipo exponencial, cuyos dos colores extremos /C0 y /C1 son arrays llanos, y las funciones stitching construidas a partir de ellas. Un color /Background se convierte con el resto. Un shading definido por una tabla muestreada o un programa PostScript conserva su definición, igual que los tipos de malla 4 a 7, que guardan los colores dentro de un stream en lugar de una función. Reescribir cualquiera de los dos es regeneración, un trabajo distinto de convertir un color
Encontrar los degradados es una trampa en sí. Las herramientas de diseño rara vez dan a un shading un objeto propio; suele vivir inline dentro del diccionario de patrón que lo pinta, o dentro de los recursos /Shading de una página. Cuando construimos el pase de degradados, un escaneo de los objetos de nivel superior casi no encontró ninguno. La búsqueda ahora sigue el grafo de objetos, acotada a doce niveles de profundidad y visitando cada número de objeto una vez, así que un recurso compartido por todas las páginas se convierte una vez y un archivo que apunta un objeto a sí mismo no puede ciclar
Dónde encaja el recoloreado antes de un envío a imprenta
Trata RecolorDocument como normalización. Convierte valores entre espacios de dispositivo; no aplica un perfil ICC, no honra una rendering intent ni simula una condición de imprenta. Para un trabajo que tiene que igualar una prueba de contrato, un flujo guiado por perfiles es la herramienta correcta, y un pase de recoloreado es como mucho el paso que mete todo en un espacio antes de que ese flujo se ejecute
Donde gana su sitio es en el caso de producción de cada día: un envío que tiene que estar en gris y llega con un logo RGB, un informe interno que no debe imprimirse a color, un lote que un paso de preflight rechazará hasta que cada archivo use un espacio. Cualquier cosa que sale del edificio debería pasar por RecolorDocument, nunca por RecolorPage a secas, porque un archivo medio convertido pasa una comprobación visual rápida y luego falla en la imprenta. Ejecuta el veredicto en el mismo trabajo: las notas sobre el preflight PDF/A y PDF/UA y sobre el banco de trabajo de cumplimiento y firma muestran cómo
// IsValidRecolorTarget es un helper que añades tú; la librería no tiene esa llamada
function IsValidRecolorTarget(Space: Integer): Boolean;
begin
Result := (Space >= 1) and (Space <= 3);
end;
procedure NormalizeFile(const Src, Dst: string; Space: Integer);
var
Lib: TPDFlib;
begin
if not IsValidRecolorTarget(Space) then
raise EArgumentException.CreateFmt('Bad recolor target %d', [Space]);
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(Src, '') <> 1 then
raise Exception.CreateFmt('Could not open %s', [Src]);
if Lib.RecolorDocument(Space) = 0 then
WriteLn(Src, ': already in the target space, copied unchanged');
Lib.SaveToFile(Dst);
finally
Lib.Free;
end;
end;
Una comprobación rápida que puedes correr sobre la salida: abre el contenido de una página convertida en cualquier inspector PDF tras una conversión a gris. El contenido propio de la página ya no debería contener operadores rg, RG, k ni K. El color que sobrevive al pase viene de los casos de arriba: un JPEG, un color directo o atado a perfil, un degradado muestreado o de malla, o ilustración dibujada dentro de un form XObject, cuyas instrucciones viven en un stream propio en lugar del contenido de página
Si además construyes la ilustración vectorial en lugar de solo convertirla, el recorrido de gráficos vectoriales, shaders y degradados muestra cómo se crean shadings y patrones en primer lugar, lo que hace más predecibles las reglas de degradados de arriba
El pase de color corre en el propio proceso: PDF Library for Delphi es Pascal nativo para Delphi, C++Builder y Lazarus, así que normalizar un lote de archivos entrantes no necesita convertidor externo ni herramienta de línea de comandos en el servidor. La página de producto de PDF Library for Delphi lista el resto de su API de procesamiento de documentos