Un encabezado relleno con 1 0 0 rg, rojo puro, vuelve de una conversión a 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 distinguirse. Esos números no son un accidente de redondeo: son los pesos de luminancia de 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 uno en grises
Qué calcula RecolorDocument para cada color
PDF Library for Delphi expone la conversión como RecolorDocument(TargetSpace) sobre TPDFlib, con 1 para grises, 2 para RGB y 3 para CMYK. Devuelve la cantidad de cosas que convirtió, y la geometría, las posiciones de texto y la ubicación de imágenes se quedan donde estaban. Solo cambian los valores de color y los nombres de espacio de color alrededor
Todo color pasa por RGB en su camino al destino. El gris es la suma de luminancia 0.299 R + 0.587 G + 0.114 B. CMYK llega a RGB por la regla de device simple de ISO 32000-1 §10.3, donde cada canal es uno menos la suma de su tinta y el negro, clampeado a cero. RGB llega a CMYK metiendo el componente más grande en el negro: K = 1 - max(R, G, B), y las tintas restantes se escalan de lo que queda. Unos resultados que vale la pena conocer antes de mandar cualquier cosa a imprenta:
- 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 - RGB
1 0.5 0(naranja) se vuelve0 0.5 1 0 k, sin nada de negro - Un rich black como
0.6 0.4 0.4 1 kse vuelve0 gcamino a grises, así que las tintas extra simplemente desaparecen - 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 a lo sumo tres decimales y siempre con punto como separador decimal, sea cual sea el locale de la máquina que corre el job. ISO 32000-1 §7.3.3 no permite otra cosa en un content stream, y un convertidor que formatee números con el locale del sistema escribirá 0,5 g en una estación de trabajo alemana, 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 = grises, 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 conversiones que de verdad cruzan de un espacio a otro, así que un documento que ya está en grises reporta 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 la portada a color?
RecolorPage convierte los operadores de color de la página seleccionada y hasta ahí llega. Las imágenes y los gradientes son objetos a nivel de documento, a menudo compartidos por varias páginas, y solo RecolorDocument los convierte. Así que la manera obvia de convertir solo una portada, seleccionar la página 1 y llamar a RecolorPage(3), le da encabezados y filetes en CMYK sobre una fotografía RGB
Esa separación es deliberada. Un image XObject dibujado en la portada puede estar también dibujado en la página 12, y convertirlo por una página lo convierte para ambas. Una llamada con alcance de página que metiera mano silenciosamente en 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 de la portada únicamente; imágenes y gradientes 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értalo entero
Lib.RecolorDocument(3);
Lib.SaveToFile('all-cmyk.pdf');
Mire el valor de retorno en ese snippet. Ambas llamadas devuelven 0 para un destino fuera de 1 a 3, y 0 también es la respuesta para una página que no necesitó cambios, así que un typo en el destino se ve exactamente igual que un documento que ya estaba correcto. Valide usted el argumento antes de la llamada. RecolorDocument restaura la página que estuviera seleccionada antes de correr, lo que significa que puede llamarlo en el medio de un job página por página sin volver a seleccionar después
Un detalle más de alcance. La pasada 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 device space. Cuando /DeviceRGB cs precede a un sc convertido, el nombre del espacio también se reescribe al destino. Salte ese renombrado en un convertidor escrito a mano y el lector queda 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 grises con solo sus operadores manejados sigue mostrando fotografías a color en páginas grises, lo que se lee peor que el original o que una conversión completa. RecolorDocument convierte una imagen cuando tiene ocho bits por componente, un device color space, y o bien ninguna compresión o Flate plain sin parámetros de decode
Las imágenes indexadas toman otro camino. Sus samples son índices a una tabla de colores, así que la tabla se convierte y cada sample conserva el índice que ya tenía. Un screenshot o un chart guardado como imagen de paleta de 256 colores se convierte así sin importar cómo estén empacados o comprimidos sus samples, porque los samples jamás se decodifican. La base de la paleta tiene que ser un device space; una paleta sobre un perfil ICC conserva su tabla
Una imagen JPEG o codificada con fax conserva sus bytes. Cambiarle los colores significaría decodificarla, convertirla y recomprimirla, lo que agrega una segunda generación de artefactos JPEG y cambia el tamaño del archivo de maneras que nadie pidió. Si un job de verdad necesita cada foto en grises, la re-codificación es una decisión aparte con su propia calidad, y preferimos que el llamador la tome explícitamente a que una conversión de color la tome por él
Los soft masks y los stencil masks se quedan como están por una razón distinta. Un soft mask suele ser una imagen DeviceGray, pero sus valores significan cobertura. Convertir uno a RGB de tres componentes hace que un visor deje de tratarlo como transparencia; termina pintado como artwork gris encima del contenido que se supone moldea. RecolorDocument colecta cada objeto referenciado vía /SMask o /Mask antes de convertir cualquier imagen, y una imagen con /ImageMask true no tiene color que convertir para empezar
Los colores fijados vía espacios ICCBased, Separation, Indexed o Pattern a nivel de operador también quedan tal como están escritos. Convertir un spot color o un valor atado a perfil a gris de device sin leer su perfil cambiaría cómo se ve la página, y una pasada cuyo trabajo es conservar la apariencia no puede ponerse a adivinar qué quería decir un perfil
Gradientes: el espacio de color y los stops se mueven juntos
Un shading declara su espacio de color una vez y le entrega cada stop a una función. Renombre /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 gradiente de azul a blanco renderiza entonces como una banda que arranca en negro. RecolorDocument recorre primero el árbol completo de funciones en modo lectura y no convierte nada salvo que cada nodo suyo sea convertible; solo entonces reescribe los stops y renombra el espacio
Las funciones que reescribe son las exponenciales, cuyos dos colores extremos /C0 y /C1 son arrays corrientes, y las stitching functions construidas a partir de ellas. Un color /Background se convierte con el resto. Un shading definido por una tabla sampleada o un programa PostScript conserva su definición, igual que los tipos de mesh 4 a 7, que guardan los colores dentro de un stream en lugar de una función. Reescribir cualquiera de esos es regeneración, un job distinto de convertir un color
Encontrar los gradientes es una trampa en sí misma. Las herramientas de diseño rara vez le dan a un shading un objeto propio; suele sentarse inline dentro del pattern dictionary que lo pinta, o dentro de los recursos /Shading de una página. Cuando construimos la pasada de gradientes, un escaneo de 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 recoloreo antes de una entrega a imprenta
Trate RecolorDocument como normalización. Convierte valores entre device spaces; no aplica un perfil ICC, no honra un rendering intent ni simula una condición de imprenta. Para un job que tiene que coincidir con una prueba de contrato, un flujo guiado por perfiles es la herramienta correcta, y una pasada de recoloreo es a lo sumo el paso que mete todo en un solo espacio antes de que ese flujo corra
Donde se gana la vida es en el caso de producción cotidiano: una entrega que tiene que ser en grises y llega con un logo RGB, un reporte interno que no debe imprimirse a color, un lote que un paso de preflight rechazará hasta que cada archivo use un solo espacio. Cualquier cosa que sale del edificio debería pasar por RecolorDocument, nunca por RecolorPage solo, porque un archivo medio convertido pasa un chequeo visual rápido y luego falla en la imprenta. Corra el veredicto en el mismo job: las notas sobre preflight PDF/A y PDF/UA y sobre el workbench de compliance y firma muestran cómo
// IsValidRecolorTarget es un helper que usted agrega; la librería no tiene tal 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;
Un chequeo rápido que puede correr sobre la salida: abra el contenido de una página convertida en cualquier inspector de PDF después de una conversión a grises. El contenido propio de la página no debería contener ya ningún operador rg, RG, k o K. El color que sobrevive a la pasada viene de los casos de arriba: un JPEG, un color atado a perfil o spot, un gradiente sampleado o de mesh, o artwork dibujado dentro de un form XObject, cuyas instrucciones viven en un stream propio en vez del contenido de la página
Si usted además construye el artwork vectorial en vez de solo convertirlo, el recorrido de gráficos vectoriales, shaders y gradientes muestra cómo se crean los shadings y patterns en primer lugar, lo que hace las reglas de gradientes de arriba más fáciles de predecir
La pasada 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 un convertidor externo ni herramienta de línea de comando en el servidor. La página del producto PDF Library for Delphi lista el resto de su API de procesamiento de documentos