Artículo técnico

Sobimpresión CMYK y dispositivos de render en HotPDF

Una caja cian se sienta sobre un fondo amarillo, y el estado de gráficos que la pinta lleva /op true y /OPM 1. En pantalla el solape es cian plano. En la hoja de imprenta el mismo solape sale verde, porque el RIP dejó la plancha del amarillo en paz y puso el cian encima. Los dos resultados siguen el archivo al pie de la letra; el render de pantalla simplemente nunca preguntó qué hace un dispositivo de imagen con ese flag, así que una aprobación hecha en pantalla cubre un color que nunca llega al papel

¿Por qué una página imprime distinto de como se previsualiza?

La sobreimpresión es una instrucción al dispositivo de salida sobre las planchas, y una pantalla RGB no tiene planchas. ISO 32000-1 §8.6.7 define qué pasa cuando la entrada OP (trazo) u op (relleno) de un ExtGState es verdadera: los colorantes que la pintura actual no nombra conservan lo que ya hubiera en su plancha, en lugar de borrarse a cero. Un objeto cian pintado así no quita nada de la plancha del amarillo. Un visor que compone en RGB no tiene dónde guardar el amarillo, así que pinta el cian por encima

Ninguna de las dos salidas está rota. La discrepancia está construida dentro de los dos modelos de imagen, y se queda invisible hasta que alguien sostiene una prueba impresa junto al monitor. Eso es tarde en el trabajo para un diseñador que puso texto negro en sobreimpresión por error, o que dejó un logo blanco en sobreimpresión esperando que se viera

Comparación de overprint CMYK en HotPDF donde un visor simple elimina el amarillo bajo el cian mientras la vista previa de overprint prueba ambas tintas de proceso y expone texto blanco sobreimpreso accidental
Un visor sencillo pinta solo la tinta superior mientras que la prueba con overprint combina ambas tintas, exponiendo trampas como texto blanco sobreimpreso por accidente

Activar la prueba con RenderOverprintPreview

HotPDF expone la prueba como un Boolean del documento, THotPDF.RenderOverprintPreview, que vale False por defecto. Con él activado, el render a bitmap rastrea las planchas de proceso bajo cada pintura en sobreimpresión y aplica las reglas de §8.6.7 antes de escribir el píxel, así que los solapes muestran la combinación de tintas que produciría la imprenta. Dejar el valor por defecto apagado es lo correcto para visualización ordinaria, porque los lectores de un PDF de oficina esperan que se vea como lo muestra su visor habitual. Actívalo en el paso cuya salida va al operador de preimpresión

var
  Pdf: THotPDF;
  Device: THPDFBitmapRenderDevice;
  Proof: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('cover-cmyk.pdf');
    Pdf.RenderOverprintPreview := True;    // prueba las planchas, por defecto False
    Device := THPDFBitmapRenderDevice.Create;
    try
      if Pdf.RenderLoadedPageToDevice(0, 150, Device) then
      begin
        Proof := Device.TakeBitmap;        // el caller es dueño de Proof desde aquí
        try
          Image1.Picture.Assign(Proof);
        finally
          Proof.Free;
        end;
      end;
    finally
      Device.Free;
    end;
  finally
    Pdf.Free;
  end;
end;

Las reglas de planchas dependen del espacio de color de la pintura en sobreimpresión, y valen la pena conocerlas porque cada una explica una sorpresa distinta en una prueba:

  • DeviceCMYK bajo /OPM 1: un componente a cero deja su plancha intacta, uno distinto de cero la reemplaza. Cian 100 / amarillo 0 sobre un fondo amarillo conserva el amarillo y se vuelve verde
  • DeviceGray: la pintura nombra solo la plancha del negro, así que el cian, el magenta y el amarillo de debajo sobreviven. Un logo blanco DeviceGray puesto en sobreimpresión sobre un fondo CMY desaparece por tanto
  • Separation y DeviceN: un colorante llamado Cyan, Magenta, Yellow o Black reemplaza esa plancha de proceso directamente, /None no pinta nada, y /All escribe todas las planchas de proceso
  • Colorantes planos: cualquier otro nombre se trata como una tinta plana; su transformación de tint da un color alternativo, que se multiplica sobre las planchas de proceso de debajo, así que la tinta oscurece lo que cubre en lugar de reemplazarlo

La prueba aplica a paths de relleno y de trazo y al texto, incluido el texto en modo de render de trazo, donde decide el flag de trazo OP en lugar de op. Las imágenes, las imágenes en línea y los sombreados sh siguen por sus pipelines de color ráster completos y pintan con su opacidad ordinaria. En la práctica eso cubre lo que los informes de preflight marcan más: reglas de sobreimpresión, keylines, tipografía y tintas planas

La transparencia sigue viva con la prueba activada. Un grupo de transparencia con knockout puesto sigue haciendo knockout, y el grupo se compone en la misma pasada que las reglas de sobreimpresión, así que un archivo listo para imprenta con arte transparente sobre un fondo negro en sobreimpresión prueba ambos efectos juntos. Acertar con uno y fallar con el otro solo produciría una prueba equivocada distinta

El ajuste también participa en la identidad de los renders cacheados. RenderLoadedPageToBitmapCached construye sus claves con página, DPI y una variante construida de los ajustes de render que incluye RenderOverprintPreview, y el nivel de disco usa la misma variante. Un visor que alterna entre vista normal y vista de prueba obtiene dos entradas de caché separadas y nunca necesita una llamada a InvalidateRenderedPageCache solo porque el conmutador cambió

La trampa del /OPM 0 en archivos DeviceCMYK

Este es el caso que manda a la gente a buscar un bug. Un archivo pone /op true sobre un relleno cian DeviceCMYK pero deja OPM en su valor por defecto 0, y entonces la prueba sigue mostrando el cian eliminando el amarillo. La prueba está en lo cierto. Bajo el modo de sobreimpresión 0, §8.6.7 dice que una pintura DeviceCMYK fija los cuatro colorantes de proceso, ceros incluidos, así que el componente amarillo a cero reemplaza la plancha del amarillo exactamente como lo haría un knockout. El modo de sobreimpresión 0 solo protege las planchas que la pintura no nombra, lo que para un color CMYK de cuatro componentes significa las planchas planas y nada más

En las planchas de proceso que muestra una prueba compuesta, el modo 0 y un knockout simple dan los mismos píxeles, así que HotPDF pinta esos objetos DeviceCMYK como knockouts ordinarios. Para que un objeto CMYK haga sobreimpresión sobre otras tintas de proceso, el ExtGState que lleva /op true también necesita /OPM 1. Cuando una prueba y la intención del diseñador discrepan sobre un objeto DeviceCMYK, abre ese ExtGState y busca la entrada OPM antes de sospechar de cualquier otra cosa

La sorpresa contraria también existe. Bajo /OPM 1, el blanco CMYK son cuatro ceros, y cuatro ceros bajo sobreimpresión dejan cada plancha como estaba. Una tipografía blanca puesta en sobreimpresión se esfuma en la hoja, y la prueba muestra cómo se esfuma, que es toda la razón para ejecutar una

Tres dispositivos detrás de RenderLoadedPageToDevice

THotPDF.RenderLoadedPageToDevice(PageIndex, DPI, Device) es un único punto de entrada cuyo objetivo decide el objeto de dispositivo que le pases. Lanza una excepción ante un dispositivo nil, trata un DPI menor que 1 como 150, y devuelve lo que el dispositivo informe desde su método Execute. THPDFRenderDevice es la base abstracta con dos miembros virtuales: Kind, que devuelve rdkBitmap, rdkDeviceContext o rdkEnhancedMetafile, y Execute(Document, PageIndex, DPI)

Los tres dispositivos incluidos son dueños de su salida a su manera:

  • THPDFBitmapRenderDevice guarda un TBitmap en su propiedad Bitmap y lo libera en su destructor salvo que TakeBitmap te lo haya entregado antes
  • THPDFDeviceContextRenderDevice.Create(ADC, AWidth, AHeight) dibuja en un HDC que ya tienes y no es dueño de nada; el constructor lanza ante un DC cero o un tamaño no positivo, y no se pinta ningún fondo, así que un DC de pantalla necesita su relleno blanco propio
  • THPDFMetafileRenderDevice guarda un TMetafile hasta que TakeMetafile lo transfiere. El texto y los paths vectoriales siguen siendo escalables en el enhanced metafile, mientras que los XObjects de imagen llegan rasterizados
Arquitectura de render de HotPDF abriendo una llamada RenderLoadedPageToDevice en abanico hacia dispositivos de render de bitmap, contexto de dispositivo de impresora y metafichero mejorado, con sus valores Kind y reglas de propiedad
Una sola llamada de renderizado se ramifica hacia dispositivos de mapa de bits, contexto de dispositivo y metafile cuyo Kind informa del objetivo y cuyos métodos Take transfieren la propiedad

La propiedad sigue las reglas normales de Delphi. Lee la propiedad Bitmap o Metafile cuando el objeto solo necesita vivir tanto como el dispositivo; llama al método Take cuando tiene que sobrevivirle. Toma el bitmap, libera el dispositivo, olvida el bitmap, y la fuga crece con el número de páginas: invisible en una muestra de cinco páginas, evidente en un lote de quinientas. Dale a cada objeto su propio try/finally y la pregunta de quién libera qué se responde sola

Despacha por Kind en el código de la aplicación en lugar de probar is THPDFBitmapRenderDevice. Como Execute y Kind son métodos virtuales públicos, puedes escribir tu propia clase de dispositivo, y el código que comprueba el tipo sigue funcionando cuando uno tuyo haga de dispositivo incluido

¿Por qué un render a impresora aterriza en una esquina?

La vía de contexto de dispositivo dimensiona la página desde el argumento DPI, no desde la anchura y altura que le pasas. El renderer escala las unidades PDF por DPI / 72 y usa la altura solo para anclar el origen de la página en el fondo de la superficie. Fija 300 a pelo y manda el trabajo a una impresora de 600 dpi, y la página sale a mitad de tamaño, empujada a la esquina inferior izquierda de la hoja. La resolución de impresora es una propiedad del driver, así que pídesela al driver:

procedure PrintPage(Pdf: THotPDF; PageIndex: Integer);
var
  Device: THPDFDeviceContextRenderDevice;
  PrinterDPI, W, H, Tmp: Integer;
  L, B, R, T: Single;
begin
  if not Pdf.GetLoadedPageVisibleBox(PageIndex, L, B, R, T) then
    Exit;
  Printer.BeginDoc;
  try
    // pregunta al driver; 300, 600 y 1200 dpi son todos habituales
    PrinterDPI := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
    W := Round((R - L) * PrinterDPI / 72);
    H := Round((T - B) * PrinterDPI / 72);
    if (Pdf.GetLoadedPageRotation(PageIndex) mod 180) = 90 then
    begin
      Tmp := W; W := H; H := Tmp;            // intercambia para páginas 90 / 270
    end;
    Device := THPDFDeviceContextRenderDevice.Create(
      Printer.Canvas.Handle, W, H);
    try
      Pdf.RenderLoadedPageToDevice(PageIndex, PrinterDPI, Device);
    finally
      Device.Free;
    end;
  finally
    Printer.EndDoc;
  end;
end;

La anchura y la altura salen de la caja visible de la página, la CropBox recortada por la MediaBox, a la resolución propia de la impresora, y quien llama las intercambia para páginas rotadas antes de crear el dispositivo. Eso coincide con lo que el renderer espera cuando ancla el origen

La prueba de sobreimpresión no baja por este camino. Los dispositivos de contexto de dispositivo y de metafile emiten llamadas GDI directamente al objetivo, y las reglas de planchas necesitan los píxeles ya existentes bajo cada pintura, que solo tiene el render a bitmap. Para una prueba en papel, renderiza el bitmap de la prueba y estíralo sobre el canvas de la impresora. Un pequeño dispositivo propio mantiene eso detrás del mismo punto de entrada:

type
  // un dispositivo tuyo: bitmap de prueba, luego estirado sobre la impresora
  TPrinterProofDevice = class(THPDFRenderDevice)
  public
    function Kind: THPDFRenderDeviceKind; override;
    function Execute(Document: THotPDF; PageIndex, DPI: Integer): boolean;
      override;
  end;

function TPrinterProofDevice.Kind: THPDFRenderDeviceKind;
begin
  Result := rdkDeviceContext;
end;

function TPrinterProofDevice.Execute(Document: THotPDF;
  PageIndex, DPI: Integer): boolean;
var
  Proof: TBitmap;
  OldProof: Boolean;
begin
  OldProof := Document.RenderOverprintPreview;
  Document.RenderOverprintPreview := True;
  try
    Proof := Document.RenderLoadedPageToBitmap(PageIndex, DPI);
  finally
    Document.RenderOverprintPreview := OldProof;  // deja el ajuste del caller como estaba
  end;
  Result := Proof <> nil;
  if Result then
  try
    Printer.Canvas.StretchDraw(
      Rect(0, 0, Printer.PageWidth, Printer.PageHeight), Proof);
  finally
    Proof.Free;
  end;
end;

Llámalo entre Printer.BeginDoc y Printer.EndDoc con un DPI que dé al bitmap suficientes píxeles para la hoja, unos 200 a 300 para una prueba tamaño carta. El rectángulo de estirado de arriba llena el área imprimible; conserva dentro la relación de aspecto de la página si la página y el papel difieren de forma

Dónde encaja la prueba junto al preflight

La prueba responde una pregunta: qué tintas sobreviven donde los objetos se solapan. Se compone sobre el mismo bitmap RGB de 24 bits que cualquier otro render, así que sus colores muestran la combinación de tintas y no un color de hoja medido. Las condiciones de imprenta son una cuestión de gestión del color, y el documento las maneja con ConfigureRenderICCWorkflow, que toma un perfil de prueba, un perfil de salida y un flag de compensación de punto negro, y ClearRenderICCWorkflow para soltarlos

El sitio más productivo para un render de prueba es junto a las comprobaciones que ya ejecutas. Una pasada de preflight informa de que el texto negro está en sobreimpresión; el render de prueba le muestra al operador qué hace eso en la página; los dos aterrizan en el mismo informe. El trabajo de packaging tiende a mezclar sobreimpresión con tintas planas, y el recorrido de el render de colores planos Separation y DeviceN cubre el lado de colorantes de la misma página, incluidas las vistas de una sola plancha con RenderLoadedPageSeparationPlate. Los artículos sobre renderizar una página PDF a un bitmap y imprimir un PDF cargado con TPrinter cubren los dos objetivos de dispositivo en su forma llana

Todo esto corre en código nativo de Delphi y C++Builder sin ninguna DLL de render que distribuir junto al ejecutable. La página del componente HotPDF para Delphi lista las funciones de renderizado y preimpresión y tiene una build de prueba para ejecutar estos ejemplos contra tus propios archivos de imprenta