Technisch artikel

Een PDF naar één kleurruimte converteren in Delphi

Een kop gevuld met 1 0 0 rg, puur rood, komt uit een grijze conversie terug als 0.299 g. Puur blauw belandt op 0.114 g, dicht genoeg bij zwart dat een marineblauw logo en de lopende tekst moeilijk uit elkaar te houden zijn. Die getallen zijn geen afrondingstoeval: het zijn de luminantiegewichten van BT.601, dezelfde die een viewer toepast wanneer hij een RGB-pagina toont op een grijs apparaat, en ze beslissen hoeveel contrast overleeft wanneer een kleurenbrochure grijs moet worden

Wat RecolorDocument per kleur berekent

PDF Library for Delphi stelt de conversie beschikbaar als RecolorDocument(TargetSpace) op TPDFlib, met 1 voor grijs, 2 voor RGB en 3 voor CMYK. Hij geeft het aantal geconverteerde dingen terug, en geometrie, tekstposities en afbeeldingsplaatsing blijven waar ze waren. Alleen de kleurwaarden en de kleurruimtenamen eromheen veranderen

Elke kleur gaat op weg naar het doel door RGB. Grijs is de luminantiesom 0.299 R + 0.587 G + 0.114 B. CMYK bereikt RGB via de kale apparaatregel uit ISO 32000-1 §10.3, waarbij elk kanaal één min de som van zijn inkt en zwart is, afgekapt op nul. RGB bereikt CMYK door de grootste component in zwart te trekken: K = 1 - max(R, G, B), en de overige inkten worden geschaald op wat overblijft. Een paar uitkomsten die goed zijn om te kennen voordat u iets naar een drukker stuurt:

  • Een grijs van 0.4 g naar CMYK geconverteerd wordt 0 0 0 0.6 k, uitsluitend zwarte inkt, wat een pers wil voor tekst en lijnen
  • RGB 1 0.5 0 (oranje) wordt 0 0.5 1 0 k, zonder enig zwart
  • Een rich black zoals 0.6 0.4 0.4 1 k wordt 0 g op weg naar grijs, dus de extra inkten verdwijnen simpelweg
  • Puur cyaan, 1 0 0 0 k, wordt 0.701 g, lichter dan de meeste mensen verwachten van een verzadigde inkt

Geconverteerde operands worden met hooguit drie decimalen weggeschreven en altijd met een punt als decimaalscheiding, wat de locale van de machine die de taak draait ook is. ISO 32000-1 §7.3.3 staat in een content stream niets anders toe, en een converter die getallen opmaakt met de systeemlocale schrijft op een Duits werkstation 0,5 g, wat een reader parseert als het getal 0 gevolgd door een onbekende operator

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 = grijs, 2 = RGB, 3 = CMYK
    WriteLn(Format('%d colors, images and gradients converted', [Converted]));
    Lib.SaveToFile('brochure-gray.pdf');
  finally
    Lib.Free;
  end;
end;

Een pagina waarvan de kleuren al in de doelruimte zitten houdt zijn originele contentbytes. RecolorDocument telt alleen conversies die werkelijk van de ene ruimte naar de andere oversteken, dus een document dat al grijs is meldt 0 en er wordt niets in herschreven. Op een groot bestand telt dat: een diff van invoer en uitvoer toont precies de pagina’s die veranderden

Waarom laat RecolorPage de omslagfoto in kleur?

RecolorPage converteert de kleuroperatoren op de geselecteerde pagina en houdt daar op. De afbeeldingen en verlopen zijn objecten op documentniveau, vaak gedeeld door meerdere pagina’s, en alleen RecolorDocument converteert ze. De voor de hand liggende manier om alleen een omslag te converteren, selecteer pagina 1 en roep RecolorPage(3) aan, geeft u dus CMYK-koppen en lijnen boven een RGB-foto

Die splitsing is met opzet. Een image XObject op de omslag getekend kan ook op pagina 12 worden getekend, en hem voor één pagina converteren converteert hem voor allebei. Een aanroep met paginabereik die stilletjes in gedeelde resources zou rondkijken, verandert pagina’s die de caller nooit selecteerde. Dat is de ergere verrassing van de twee, dus raakt de per-pagina-aanroep alleen wat aan de pagina toebehoort

// Operatoren alleen op de omslag; afbeeldingen en verlopen worden hier niet geconverteerd
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');

// Voor een document dat doorlopend één ruimte moet zijn, converteer het geheel
Lib.RecolorDocument(3);
Lib.SaveToFile('all-cmyk.pdf');

Houd in dat fragment de returnwaarde in de gaten. Beide aanroepen geven 0 terug voor een doel buiten 1 tot en met 3, en 0 is ook het antwoord voor een pagina die geen verandering nodig had, dus een typefout in het doel ziet er precies uit als een document dat al correct was. Valideer het argument zelf vóór de aanroep. RecolorDocument herstelt de pagina die voor zijn run was geselecteerd, wat betekent dat u hem midden in een taak pagina voor pagina kunt aanroepen zonder daarna opnieuw te selecteren

Nog een detail over het bereik. De operatorpasse leest de eigen content stream van elke pagina, de tekeninstructies die op de pagina zelf staan, en converteert g, rg, k, hun streekvormen, en sc / scn wanneer de huidige ruimte een apparaatruimte is. Gaat /DeviceRGB cs aan een geconverteerde sc vooraf, dan wordt ook de ruimtenaar naar het doel herschreven. Slaat u die hernoeming over in een handgeschreven converter, dan blijft een reader zoeken naar drie RGB-componenten waar nu één grijswaarde staat

Afbeeldingen en paletten: welke veranderen, welke hun bytes houden

Afbeeldingen tellen net zo zwaar als operatoren. Een brochure naar grijs geconverteerd met alleen de operatoren behandeld, toont nog steeds kleurenfoto’s op grijze pagina’s, wat slechter leest dan zowel het origineel als een volledige conversie. RecolorDocument converteert een afbeelding wanneer hij acht bits per component heeft, een apparaatkleurruimte, en ofwel geen compressie of kale Flate zonder decodeparameters

Geïndexeerde afbeeldingen nemen een andere route. Hun samples zijn indexen in een kleurtabel, dus de tabel wordt geconverteerd en elk sample houdt de index die hij al droeg. Een screenshot of grafiek opgeslagen als paletafbeelding met 256 kleuren converteert zo ongeacht hoe zijn samples zijn ingepakt of gecomprimeerd, want de samples worden nooit gedecodeerd. De basis van het palet moet een apparaatruimte zijn; een palet boven een ICC-profiel houdt zijn tabel

Diagram dat toont welke delen van een PDF RecolorDocument in Delphi herschrijft en welke delen, zoals JPEG-afbeeldingen en soft masks, byte-identiek doorgaan
Een RecolorDocument-pass herschrijft operatoren, in aanmerking komende afbeeldingen, geïndexeerde paletten en verloopparen, terwijl JPEG-bytes, masks en aan profielen gebonden kleuren onaangeroerd meereizen

Een JPEG- of faxgecodeerde afbeelding houdt zijn bytes. Zijn kleuren veranderen zou betekenen dat hij wordt gedecodeerd, geconverteerd en opnieuw gecomprimeerd, wat een tweede generatie JPEG-artefacten toevoegt en de bestandsgrootte verandert op manieren die niemand vroeg. Als een taak werkelijk elke foto in grijs nodig heeft, is hercoderen een aparte beslissing met een eigen kwaliteitsinstelling, en we laten die beslissing liever expliciet bij de caller dan dat een kleurconversie haar voor hem neemt

Soft masks en stencil masks blijven zoals ze zijn om een andere reden. Een soft mask is meestal een DeviceGray-afbeelding, maar zijn waarden betekenen dekking. Hem naar drie-componenten RGB converteren en een viewer behandelt hem niet meer als transparantie; hij belandt als grijs artwork bovenop de content die hij had moeten vormen. RecolorDocument verzamelt elk object dat via /SMask of /Mask wordt gerefereerd voordat hij een afbeelding converteert, en een afbeelding met /ImageMask true heeft in de eerste plaats geen kleur om te converteren

Kleuren gezet via ICCBased-, Separation-, Indexed- of Pattern-ruimtes op operatorniveau worden ook gelaten zoals geschreven. Een steunkleur of een profielgetagde waarde naar apparaatgrijs omzetten zonder het profiel te lezen zou veranderen hoe de pagina eruitziet, en een passe waarvan de taak is het aanzicht te houden kan niet beginnen te raden wat een profiel bedoelde

Verlopen: de kleurruimte en de stops verplaatsen zich samen

Een shading declareert zijn kleurruimte één keer en geeft elke stop aan een functie. Hernoem /ColorSpace van DeviceRGB naar DeviceGray terwijl de functie nog steeds drie getallen teruggeeft, en een reader neemt de eerste van die drie, het rode kanaal, als grijswaarde. Een verloop van blauw naar wit rendert dan als een band die bij zwart begint. RecolorDocument loopt eerst de hele functieboom alleen-lezen door en converteert niets tenzij elke knoop erin kan worden geconverteerd; pas daarna herschrijft hij de stops en hernoemt de ruimte

De functies die hij herschrijft zijn het exponentiële type, waarvan de twee eindkleuren /C0 en /C1 kale arrays zijn, en stitching-functies die eruit zijn opgebouwd. Een /Background-kleur wordt met de rest geconverteerd. Een shading gedefinieerd door een gesampled tabel of een PostScript-programma houdt zijn definitie, net als de mesh-types 4 tot en met 7, die kleuren in een stream bewaren in plaats van in een functie. Een van beide herschrijven is regeneratie, een andere klus dan een kleur converteren

Diagram van PDF Library for Delphi met gradientconversie die een shading-kleurruimte koppelt aan zijn stop-functie over losse objecten, patronen en pagina-shading-resources, terwijl sampled-, PostScript- en mesh-shadings hun eigen definitie behouden
Een verloop converteren betekent zijn kleurruimte en de daarachter liggende stops samen verplaatsen, waar de schakering ook leeft, terwijl mesh- of sampled-function-schakeringen hun eigen definitie houden

De verlopen vinden is een valkuil op zich. Designtools geven een shading zelden een eigen object; hij zit meestal inline in de pattern-dictionary die hem schildert, of in de /Shading-resources van een pagina. Toen we de gradientpasse bouwden, vond een scan van top-level-objecten er bijna geen. De zoektocht volgt nu de objectgraaf, begrensd op twaalf niveaus diep en elk objectnummer één keer bezoekend, zodat een door elke pagina gedeelde resource één keer wordt geconverteerd en een bestand dat een object naar zichzelf laat wijzen niet kan lussen

Waar omkleuren past vóór een drukwerk-inzending

Bouw RecolorDocument als normalisatie. Hij converteert waarden tussen apparaatruimtes; hij past geen ICC-profiel toe, eert geen rendering intent en simuleert geen persomstandigheid. Voor een klus die moet matchen met een contractproef is een op profielen gebaseerde workflow het juiste gereedschap, en een omkleurpasse is hooguit de stap die alles in één ruimte brengt voordat die workflow draait

Waar hij zijn plaats verdient is het alledaagse productiegeval: een inzending die grijs moet zijn en binnenkomt met een RGB-logo, een intern rapport dat niet in kleur mag printen, een batch die een preflight-stap weigert totdat elk bestand één ruimte gebruikt. Alles dat het gebouw verlaat hoort door RecolorDocument te gaan, nooit door RecolorPage alleen, want een half geconverteerd bestand haalt een snelle visuele controle en faalt dan bij de drukker. Draai het oordeel in dezelfde taak: de aantekeningen bij PDF/A- en PDF/UA-preflight en bij de compliance- en signing-workbench laten zien hoe

// IsValidRecolorTarget is een helper die u zelf toevoegt; de library heeft zo’n aanroep niet
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;

Een snelle controle die u op de uitvoer kunt draaien: open na een grijze conversie de content van een geconverteerde pagina in willekeurige PDF-inspector. De eigen content van de pagina hoort geen enkele rg-, RG-, k- of K-operator meer te bevatten. Kleur die de passe overleeft komt uit de gevallen hierboven: een JPEG, een profielgetagde of steunkleur, een gesampled of mesh-verloop, of artwork getekend binnen een form XObject, waarvan de instructies in een eigen stream wonen in plaats van in de paginacontent

Bouwt u het vectorartwork ook zelf in plaats van het alleen te converteren, dan laat de doorloop van vector graphics, shaders en gradients zien hoe shadings en patterns in de eerste plaats worden gemaakt, wat de gradientregels hierboven makkelijker voorspelbaar maakt

De kleurpasse draait in-process: PDF Library for Delphi is native Pascal voor Delphi, C++Builder en Lazarus, dus een batch inkomende bestanden normaliseren vraagt op de server geen externe converter of command-line tool. De productpagina van PDF Library for Delphi somt de rest van zijn documentverwerkings-API op