Welcome Guest! You need to login or register to make posts.

Notification

Icon
Error

Options
Go to last post Go to first unread
Todd Kneib  
#1 Posted : Wednesday, January 29, 2020 8:34:09 AM(UTC)
Todd Kneib

Rank: Advanced Member

Groups: Member
Joined: 5/10/2006(UTC)
Posts: 32

Thanks: 6 times
We are facing issues with color profiles while printing to a Windows Printer. I found this article in the forum and I think I adapted it correctly for GraphicsMill 9.3.7. I'm getting unexpected color results though when using DrawOn to draw the Aurigma bitmap to the printer's (gdi+) graphics.

If I do not apply the printers color profile, I get one result, if I apply the printer profile I get a second one. It should be noted that if I just open the image using gdi and draw it, I get the same result (color wise) as using DrawOn without the color profile.

I am using CuteWriter PDF to print the output of the code below. (The linqpad file, profile, and sample pdf outputs are in a zip file at the end of this post)
This is the test image I'm printing: ColorTestChart_ARGB_300.png

This is the result using Cute PDF with no color profile set in Windows Color Management: SamplePrintedWithCutePDFAndNo(srgb)Profile.png
The left is just DrawOn, the right is after I applied the color profile.

I then went into color management (Printer Properties -> Color Management -> Chose the CuteWriter PDF printer and assigned the OwlInk dyesub color profile, as dyesub profiles really affect the colors so it is noticeable.

Running the script again, I got the following output. Note that both the just DrawOn and the applied profile then DrawOn are both affected. SamplePrintedWithCutePDFAndOwlDyesub6ColorProfile.png

So, should I be applying the color profile as was mentioned in the original article?

Code:

string inputFile = @"C:\temp\ColorTestChart_ARGB_900.tif";
string inputProfile = @"C:\Windows\System32\spool\drivers\color\sRGB Color Space Profile.icm";

//https://www.inkowl.com/page/performance-d-icc-profiles/

void Main()
{
	PrintImage();
}

private void PrintImage()
{
	PrintDocument pd = new PrintDocument();
	pd.PrintPage += new PrintPageEventHandler(this.pd_PrintPage);
	pd.Print();
}

private void pd_PrintPage(object sender, PrintPageEventArgs ev)
{
	ColorProfile printerSurfaceProfile;
	try
	{
		IntPtr hdc = ev.Graphics.GetHdc();
		//Get HDC color profile
		printerSurfaceProfile = ColorProfile.FromHdc(hdc);
	}
	finally
	{
		ev.Graphics.ReleaseHdc();
	}

	int leftMargin = 50;
	int xPos = leftMargin;
	int yPos = 0;
	
	System.Drawing.Font f = new System.Drawing.Font("Ariel", 20, 
				System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
	ev.Graphics.PageUnit = System.Drawing.GraphicsUnit.Pixel;
	
	using (Bitmap input = new Bitmap(inputFile))
	{
		ev.Graphics.DrawString("Org w/SP (ARGB) -No PP", f, System.Drawing.Brushes.Black, xPos, yPos);
		//draw original image without printer surface profile
		input.DrawOn(ev.Graphics, xPos, yPos + 40, CombineMode.Alpha, 1);
		xPos += input.Width + 10;
		
		ConvertInternalImageToColorProfile(input, inputProfile, printerSurfaceProfile, ColorTransformationIntent.Perceptual);

		ev.Graphics.DrawString("Org w/SP (ARGB) w/PP", f, System.Drawing.Brushes.Black, xPos, yPos);
		//draw original image with printer driver surface profile applied
		input.DrawOn(ev.Graphics, xPos, yPos + 40, CombineMode.Alpha, 1);
		xPos = leftMargin;

		yPos += input.Height + 40;
	}

	using (var gdiBmp = new System.Drawing.Bitmap(inputFile))
	{
		ev.Graphics.DrawString("Org GDI w/SP (ARGB) -No PP", f, System.Drawing.Brushes.Black, xPos, yPos);
		//draw original image without printer surface profile
		ev.Graphics.DrawImage(gdiBmp, xPos, yPos + 40);
		xPos += gdiBmp.Width + 10;
	}
}


private static void ConvertInternalImageToColorProfile(
		Aurigma.GraphicsMill.Bitmap imgToConvert, 
		string sourceProfile, 
		Aurigma.GraphicsMill.ColorProfile outputPF, 
		ColorTransformationIntent renderIntent)
{
	Aurigma.GraphicsMill.ColorProfile sourcePF = imgToConvert.ColorProfile;
	if (sourcePF == null) 
	{
		sourcePF = new ColorProfile(sourceProfile);
	}

	if (sourcePF.Description != outputPF.Description || 
		sourcePF.Model != outputPF.Model || 
		sourcePF.Manufacturer != outputPF.Manufacturer || 
		sourcePF.ColorSpace != outputPF.ColorSpace)
	{
		imgToConvert.ColorManagement.ColorManagementEngine = ColorManagementEngine.LittleCms;
		imgToConvert.ColorProfile = sourcePF;
		imgToConvert.ColorManagement.TransformationIntent = renderIntent;
		imgToConvert.ColorManagement.DestinationProfile = outputPF;
		if (outputPF.ColorSpace == ColorSpace.Cmyk)
		{
			imgToConvert.ColorManagement.Convert(PixelFormat.Format32bppCmyk);
		}
		else if (outputPF.ColorSpace == ColorSpace.Rgb)
		{
			imgToConvert.ColorManagement.Convert(PixelFormat.Format24bppRgb);
		}
	}
}


SamplePDFsProfileAndLinq.zip (2,322kb) downloaded 4 time(s).
Eugene Kosmin  
#2 Posted : Sunday, February 2, 2020 11:42:03 PM(UTC)
Eugene Kosmin

Rank: Advanced Member

Groups: Member, Administration, Moderator
Joined: 9/19/2006(UTC)
Posts: 505

Was thanked: 41 time(s) in 41 post(s)
Hi Todd,

Yes, it's still needed.

Internally drawing happens with BilBlt function, which ignores color management. I guess System.Drawing Graphics treats all RGB images as sRGB without taking into account the actual profile. So the outputs of GM without conversion and System.Drawing are identical. The converted Bitmap is slightly different, and I would say that it looks closer to the original. It's easier to notice on bright orange and violet tones.

I tried to convert the original image to a CMYK profile and run the code again. GM without conversion doesn't work at all due to unsupported pixel format exception. Two others produce a visually equal result. System.Drawing has the support of CMYK colorspace, but it's limited and inconsistent. And you have no control over color conversion if it suddenly becomes important. With GM, you can use different engines, intents, and so on.
Best regards,
Eugene Kosmin
The Aurigma Development Team
Todd Kneib  
#3 Posted : Monday, February 3, 2020 3:02:19 PM(UTC)
Todd Kneib

Rank: Advanced Member

Groups: Member
Joined: 5/10/2006(UTC)
Posts: 32

Thanks: 6 times
I looked at the BitBlt function, which by default ignores color management, but then I found SetICMMode , which says
Quote:
"Specifically, if a device independent bitmap (DIB) is used as the source for a blit, and the blit is performed into a DC that has WCS enabled, color matching will be performed."


Based on that, I added a call to SetICMMode to query the mode (updated code below) of the printer device context, and on the Cute PDF printer ICM mode is ON and on the Microsoft PDF printer it is OFF. This is causing the output between these two printers to be different.

In affect, if ICM mode is ON, then the color profile is being applied by Windows Color Mangament (WCM) and it should not be applied to the image before DrawOn (BitBlt) otherwise, the profile has been applied twice.

To test this hypothesis, I updated the code so that it uses whatever the printer ICM mode is and calls DrawOn without and with the specificed profile applied.
I then switch the ICM mode of the printer context, and DrawOn the image again without and with the profile.
For the Microsoft PDF printer the results looked like this: WinPDF_SRGB_ICMON_OFF.png
So, the upper left has no profile applied, the upper right has the profile applied using Aurigma, the lower left has the profile applied by WCM, and the lower right has the profile applied once by Aurigma and again by WCM. Using a sRGB color profile image, the upper right and lower left are the same (virtually, there are differences in the jpg artifacts).

For the CutePDF printer the results looked like this: CutePDF_SRGB_ICMON_OFF.png
Since CutePDF ICM is ON by default, the results are switched compared to the Microsoft PDF. The upper left has the profile applied by WCM, the upper right has the profile applied once by Aurigma and again by WCM, the lower left has no profile applied, and the lower right has the profile applied using Aurigma. Using a sRGB color profile image, the upper left and lower right are the same.

Note there are some slight color differences between WCM and Aurigma applying the same profile when the source image is not sRGB (AdobeRGB in this case). CutePDF_ICMON_noAG_vs_ICMOFF_withAG.png This example is the CutePDF with WCM applying the left side of each swatch (via ICM ON) and Aurigma applying the right side (via ICM OFF and applying the color transform).

Which application of the printer's profile is correct? Or do I have to put in support to either have Aurigma apply the output profile, or WCM apply the output profile?

Code:

//string inputFile = @"C:\temp\ColorTestChart_SRGB_900.tif";
string inputFile = @"C:\temp\ColorTestChart_ARGB_900.tif";
string inputProfile = @"C:\Windows\System32\spool\drivers\color\sRGB Color Space Profile.icm";

//https://www.inkowl.com/page/performance-d-icc-profiles/
string outputProfile = @"C:\Windows\System32\spool\drivers\color\Performance-D - 4 colors - Sublimation Transfer Paper - InkOwl Profile.icm";

void Main()
{
	PrintImage();
}

private void PrintImage()
{
	PrintDocument pd = new PrintDocument();
	pd.PrintPage += new PrintPageEventHandler(this.pd_PrintPage);
	pd.Print();
}

private void pd_PrintPage(object sender, PrintPageEventArgs ev)
{
	ColorProfile printerSurfaceProfile;
	ICM_MODE icmMode = ICM_MODE.ICM_QUERY;
	try
	{
		IntPtr hdc = ev.Graphics.GetHdc();
		//Get HDC color profile
		printerSurfaceProfile = ColorProfile.FromHdc(hdc);

		icmMode = (ICM_MODE)SetICMMode(hdc, (int)ICM_MODE.ICM_QUERY);
		Debug.WriteLine($"ICM Mode of the printer device context {icmMode}");
	}
	finally
	{
		ev.Graphics.ReleaseHdc();
	}

	int leftMargin = 50;
	int xPos = leftMargin;
	int yPos = 0;
	
	System.Drawing.Font f = new System.Drawing.Font("Ariel", 20, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
	ev.Graphics.PageUnit = System.Drawing.GraphicsUnit.Pixel;
	int textHeight = (int)ev.Graphics.MeasureString("Testing 123", f).Height + 5;

	ev.Graphics.DrawString($"{((PrintDocument)sender).PrinterSettings.PrinterName} - Default ICM Mode: {icmMode} - Input Image: {inputFile}", f, System.Drawing.Brushes.Black, xPos, yPos);
	yPos += textHeight;
		
	int imageHeight = 10;
	using (Bitmap input = new Bitmap(inputFile))
	{
		imageHeight = input.Height;
		string profileName = input.ColorProfile != null ? input.ColorProfile.Description : "sRGB(NotSet)";
		
		ev.Graphics.DrawString($"Org w/SP ({profileName}) -No PP", f, System.Drawing.Brushes.Black, xPos, yPos);
		//draw original image without printer surface profile
		input.DrawOn(ev.Graphics, xPos, yPos + textHeight, CombineMode.Alpha, 1);
		xPos += input.Width + 20;
		
		ConvertInternalImageToColorProfile(input, inputProfile, printerSurfaceProfile, ColorTransformationIntent.Perceptual);

		ev.Graphics.DrawString($"Org w/SP ({profileName}) w/PP(Perceptual)", f, System.Drawing.Brushes.Black, xPos, yPos);
		//draw original image with printer driver surface profile applied
		input.DrawOn(ev.Graphics, xPos, yPos + textHeight, CombineMode.Alpha, 1);
		xPos += input.Width + 20;
	}

	//New line
	xPos = leftMargin;
	yPos += imageHeight + textHeight + 10;

	//swap ICM Mode
	try
	{
		IntPtr hdc = ev.Graphics.GetHdc();
		icmMode = (ICM_MODE)SetICMMode(hdc, icmMode == ICM_MODE.ICM_ON ? (int)ICM_MODE.ICM_OFF : (int)ICM_MODE.ICM_ON);
		icmMode = (ICM_MODE)SetICMMode(hdc, (int)ICM_MODE.ICM_QUERY);
		Debug.WriteLine($"ICM Mode of the printer device context, after switching is: {icmMode}");
	}
	finally
	{
		ev.Graphics.ReleaseHdc();
	}

	//Draw the images the same way, again, with the new mode
	using (Bitmap input = new Bitmap(inputFile))
	{
		string profileName = input.ColorProfile != null ? input.ColorProfile.Description : "sRGB(NotSet)";
		ev.Graphics.DrawString($"Org w/SP ({profileName}) -No PP -ICM Mode: {icmMode}", f, System.Drawing.Brushes.Black, xPos, yPos);
		//draw original image without printer surface profile
		input.DrawOn(ev.Graphics, xPos, yPos + textHeight, CombineMode.Alpha, 1);
		xPos += input.Width + 20;

		ConvertInternalImageToColorProfile(input, inputProfile, printerSurfaceProfile, ColorTransformationIntent.Perceptual);

		ev.Graphics.DrawString($"Org w/SP ({profileName}) w/PP(Perceptual) -ICM Mode: {icmMode}", f, System.Drawing.Brushes.Black, xPos, yPos);
		//draw original image with printer driver surface profile applied
		input.DrawOn(ev.Graphics, xPos, yPos + textHeight, CombineMode.Alpha, 1);
		xPos += input.Width + 20;
	}

	//New line
	xPos = leftMargin;
	yPos += imageHeight + textHeight + 10;

	using (var gdiBmp = new System.Drawing.Bitmap(inputFile))
	{
		ev.Graphics.DrawString("Org GDI w/SP (ARGB) -No PP", f, System.Drawing.Brushes.Black, xPos, yPos);
		//draw original image without printer surface profile
		ev.Graphics.DrawImage(gdiBmp, xPos, yPos + textHeight);
		xPos = leftMargin;
		yPos += gdiBmp.Height + textHeight + 10;
	}
}


private static void ConvertInternalImageToColorProfile(Aurigma.GraphicsMill.Bitmap imgToConvert, string sourceProfile, Aurigma.GraphicsMill.ColorProfile outputPF, ColorTransformationIntent renderIntent)
{
	Aurigma.GraphicsMill.ColorProfile sourcePF = imgToConvert.ColorProfile;
	if (sourcePF == null) 
	{
		sourcePF = new ColorProfile(sourceProfile);
	}

	if (sourcePF.Description != outputPF.Description || 
		sourcePF.Model != outputPF.Model || 
		sourcePF.Manufacturer != outputPF.Manufacturer || 
		sourcePF.ColorSpace != outputPF.ColorSpace)
	{
		imgToConvert.ColorManagement.ColorManagementEngine = ColorManagementEngine.LittleCms;
		imgToConvert.ColorProfile = sourcePF;
		imgToConvert.ColorManagement.TransformationIntent = renderIntent;
		imgToConvert.ColorManagement.DestinationProfile = outputPF;
		if (outputPF.ColorSpace == ColorSpace.Cmyk)
		{
			imgToConvert.ColorManagement.Convert(PixelFormat.Format32bppCmyk);
		}
		else if (outputPF.ColorSpace == ColorSpace.Rgb)
		{
			imgToConvert.ColorManagement.Convert(PixelFormat.Format24bppRgb);
		}
	}
}

enum ICM_MODE
{
ICM_OFF              = 1,
ICM_ON               = 2,
ICM_QUERY            = 3,
ICM_DONE_OUTSIDEDC   = 4
}

[DllImport("gdi32.dll", EntryPoint = "SetICMMode", SetLastError = true)]
static extern int SetICMMode([In] IntPtr hdc, int mode);


I have included the pdfs, sRGB version of the test image, and the linqPad script
SamplePDF_Linq_ICM_Modes.zip (2,580kb) downloaded 5 time(s).
Eugene Kosmin  
#4 Posted : Wednesday, February 5, 2020 6:37:20 PM(UTC)
Eugene Kosmin

Rank: Advanced Member

Groups: Member, Administration, Moderator
Joined: 9/19/2006(UTC)
Posts: 505

Was thanked: 41 time(s) in 41 post(s)
In general, conversion to the HDC color profile should be enough.

A color profile is a function, and pixel data is a source for that function. When the source goes through the function, you get a real color value. So, when you change the color profile, you need to change all the source values. Color management handles this.

PDF printers save your images without a color profile. They set colorspace as DeviceRGB, which means that the color profile is to defined by a printing/consuming device. When you see these PDFs on the screen, color information is lost.

Do you see any issues when printing on paper directly?
Best regards,
Eugene Kosmin
The Aurigma Development Team
Todd Kneib  
#5 Posted : Thursday, February 6, 2020 12:46:44 PM(UTC)
Todd Kneib

Rank: Advanced Member

Groups: Member
Joined: 5/10/2006(UTC)
Posts: 32

Thanks: 6 times
I do see similar results when actually printing to a printer, 20200206_141909.jpg using the CutePDF printer was just easier.

Having said that, I think I have figured it out while running some test prints from Photoshop.

In photoshop there are three modes to choose from when you print:
  1. Let Printer Determine Color
  2. Let Photoshop Determine Color
  3. No Color Management
Programatically this equates to:
  1. ICMMode ON, No Profile applied in software
  2. ICMMode OFF, Color profile applied in software (ColorManagement.Convert)
  3. ICMMode OFF, No Profile applied in software
*4 ICMMode ON AND Color profile applied in software, which is doubling of the color profile.

Note I tried printing from Photoshop with "Let Printer Determine" and from Windows 10 Picture viewer, and they both came out like ICMMode ON, in the programmatic list.

So, the moral of this store is, if you are applying a color profile, you need to make sure the ICMMode is off for the device context you are printing to, otherwise you could end up with 4, double applied profile.
Eugene Kosmin  
#6 Posted : Thursday, February 6, 2020 4:32:14 PM(UTC)
Eugene Kosmin

Rank: Advanced Member

Groups: Member, Administration, Moderator
Joined: 9/19/2006(UTC)
Posts: 505

Was thanked: 41 time(s) in 41 post(s)
Todd,

Thanks for sharing your experience.
Best regards,
Eugene Kosmin
The Aurigma Development Team
Users browsing this topic
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.