Determine if a color channel is used
A customer asked: “How can we determine if a process color channel is used on a page. We want to know if a document has no pixel of cyan or magenta although these color channels always exist. We could count the separations contents, but this might be unreliable in low resolutions and I wonder if there is a simpler way.“
To solve this, a custom transform can be used to scan the content to discover which color spaces are in use. This C+ example shows how it’s done.
/*
* Copyright (C) 2013-2025 Global Graphics Software Ltd. All rights reserved
*
* Simple sample application for color reporting using the JawsMako APIs.
*/
#include <exception>
#include <iostream>
#include <jawsmako/jawsmako.h>
#include <jawsmako/pdfinput.h>
#include <jawsmako/customtransform.h>
#include <edl/iabort.h>
#include <fstream>
#include <iostream>
#include <filesystem>
#include <map>
using namespace JawsMako;
using namespace EDL;
namespace fs = std::filesystem;
typedef std::map<IDOMColorSpace::eColorSpaceType, String> ColorSpaceTypeMap;
// Print usage
static void usage()
{
std::wcerr << L"Usage: <source folder> "; // required arguments
}
class CColorTransformImplementation : public ICustomTransform::IImplementation
{
public:
CColorTransformImplementation()
{
}
virtual IDOMBrushPtr transformShadingPatternBrush(IImplementation* genericImplementation, const IDOMShadingPatternBrushPtr& brush, const CTransformState& state)
{
return genericImplementation->transformShadingPatternBrush(NULL, brush, state);
}
virtual IDOMColorPtr transformColor(IImplementation* genericImplementation, const IDOMColorPtr& color, const CTransformState& state)
{
m_colorList.append(color);
return genericImplementation->transformColor(NULL, color, state);
}
CEDLVector<IDOMColorPtr> getColorList() const
{
return m_colorList;
}
private:
CEDLVector<IDOMColorPtr> m_colorList;
};
ColorSpaceTypeMap getColorSpaceMap()
{
ColorSpaceTypeMap colorSpaceMap;
colorSpaceMap.insert({ IDOMColorSpace::eDeviceRGB, L"DeviceRGB" });
colorSpaceMap.insert({ IDOMColorSpace::eDeviceGray, L"DeviceGray" });
colorSpaceMap.insert({ IDOMColorSpace::eDeviceCMYK, L"DeviceCMYK" });
colorSpaceMap.insert({ IDOMColorSpace::esRGB, L"sRGB" });
colorSpaceMap.insert({ IDOMColorSpace::esGray, L"sGray" });
colorSpaceMap.insert({ IDOMColorSpace::escRGB, L"XPS scRGB" });
colorSpaceMap.insert({ IDOMColorSpace::eICCBased, L"ICC-based" });
colorSpaceMap.insert({ IDOMColorSpace::eIndexed, L"Indexed" });
colorSpaceMap.insert({ IDOMColorSpace::eDeviceN, L"Spot" });
colorSpaceMap.insert({ IDOMColorSpace::eLAB, L"L*a*b*" });
return colorSpaceMap;
}
#ifdef _WIN32
int wmain(int argc, wchar_t* argv[])
#else
int main(int argc, char* argv[])
#endif
{
ColorSpaceTypeMap colorSpaceMap = getColorSpaceMap();
try
{
// Create our JawsMako instance.
// Currently only one instance is allowed at any one time.
IJawsMakoPtr jawsMako = IJawsMako::create(".");
IJawsMako::enableAllFeatures(jawsMako);
IAbortPtr abort = IAbort::create();
IProgressTickPtr progressTick;
IProgressMonitorPtr progressMonitor = IProgressMonitor::create(progressTick, abort);
// There should be at least one argument, a source file.
if (argc < 2)
{
usage();
return 1;
}
const std::wstring inputFolderPath = argv[1];
for (auto& p : fs::directory_iterator(inputFolderPath))
{
String inputFilePath = p.path().c_str();
std::wcout << L"File: " << inputFilePath << std::endl;
// Declare our input and output pointers
IInputPtr input = IPDFInput::create(jawsMako, progressMonitor);
// Get the assembly from the input.
IDocumentAssemblyPtr assembly = input->open(inputFilePath);
IDocumentPtr document = assembly->getDocument();
uint32 pageCount = document->getNumPages();
// Create a transform to gather color info
CColorTransformImplementation implementation;
ITransformPtr getColors = ICustomTransform::create(jawsMako, &implementation, abort, true, true, true, true, true, true);
std::map<String, IDOMColorPtr> inkMap;
// Apply the transform to every page
for (uint32 pageNum = 0; pageNum < pageCount; pageNum++)
{
IPagePtr page = document->getPage(pageNum);
IDOMFixedPagePtr fixedPage = page->edit();
// Get inks
inkMap.clear();
IRendererTransform::CInkInfoVect inks = IRendererTransform::findInks(jawsMako, fixedPage);
for (auto ink : inks)
{
inkMap.insert({ U8StringToString(ink.inkName), ink.inkColor });
}
// get colors
bool result = false;
getColors->transform(fixedPage, result);
}
CEDLVector<IDOMColorPtr> colors = implementation.getColorList();
std::wcout << L"Color count: " << colors.size() << std::endl;
// Process colors
int index = 0;
for (IDOMColorPtr color : colors)
{
auto colorSpace = color->getColorSpace();
auto colorSpaceEnum = colorSpace->getColorSpaceType();
String colorSpaceType = colorSpaceMap.at(colorSpaceEnum);
uint8 numComponents = colorSpace->getNumComponents();
wprintf_s(L"%3d ", ++index);
std::wcout << colorSpaceType << L" (";
for (uint32 c = 0; c < numComponents; c++)
{
wprintf_s(L"%4.2f", color->getComponentValue(c));
if (c < numComponents - 1)
std::wcout << ",";
}
std::wcout << L") ";
if (colorSpaceEnum == IDOMColorSpace::eDeviceN)
{
IDOMColorSpaceDeviceNPtr deviceN = edlobj2IDOMColorSpaceDeviceN(colorSpace);
numComponents = deviceN->getNumComponents();
for (uint8 componentIdx = 0; componentIdx < numComponents; componentIdx++)
{
auto altColorSpace = deviceN->getAlternateColorSpace();
auto altColorSpaceEnum = altColorSpace->getColorSpaceType();
IDOMDeviceNColorantPtr colorant = deviceN->getColorant(componentIdx);
String colorantName = U8StringToString(U8String(colorant->getName()));
// Only process if a not a special
if (colorantName != L"All" && colorantName != L"None")
{
IDOMColorPtr colorantColor = inkMap.at(colorantName);
std::wcout << L"('" << colorantName << L"'\t";
String altColorSpaceType = colorSpaceMap.at(altColorSpaceEnum);
// Components of the alternate color space
uint8 altNumComponents = altColorSpace->getNumComponents();
std::wcout << altColorSpaceType << L" (";
for (uint32 altComponentIdx = 0; altComponentIdx < altNumComponents; altComponentIdx++)
{
wprintf_s(L"%4.2f", colorantColor->getComponentValue(altComponentIdx));
if (altComponentIdx < (altNumComponents - 1))
std::wcout << ",";
}
std::wcout << L") ";
}
}
std::wcout << std::endl;
}
std::wcout << std::endl;
}
}
}
catch (IError& e)
{
String errorFormatString = getEDLErrorString(e.getErrorCode());
std::wcerr << L"Exception thrown: " << e.getErrorDescription(errorFormatString) << std::endl;
#ifdef _WIN32
// On windows, the return code allows larger numbers, and we can return the error code
return e.getErrorCode();
#else
// On other platforms, the exit code is masked to the low 8 bits. So here we just return
// a fixed value.
return 1;
#endif
}
catch (std::exception& e)
{
std::wcerr << L"std::exception thrown: " << e.what() << std::endl;
return 1;
}
return 0;
}