locked
Strip Gamma in generated and streamed .png images RRS feed

  • Question

  • User2015715426 posted

    Hi everyone,

     I hope to get at list some pointers in a right direction with this annoying problem.

    When you generate and stream .png images in ASP.NET (working in VB), the colors are incorrectly produced in IE. For instance the 767676 rendered as 6B6B6B. This can be corrected by resaving generated image in any plain image editor.

    I’ve read that the gamma chunk needs to be stripped off the generated image which causes problem in IE. Spend quite some time unsuccessfully trying to figure out if I can automate that in .NET
    I’ve seen it done in PHP - generated png images correctly produced in IE and by the way twice as less in size as the same image generated with ASP.NET.

    If someone has experience working with generating png image please share your senses on a problem. Is it possible to overcome this problem at all? I really wish NOT to go with Jpeg format.

    Here is a simple code for image streaming. If you view it in IE and try to read the color off the browser screen, it would be (107, 107, 017) instead of the inteneded (118,118,118)

    <%@ Page Trace="False" Language="vb" aspcompat="false" debug="true" validateRequest="false"%>
    <%
    @ Import Namespace=System.Drawing %>
    <%
    @ Import Namespace=System.Drawing.Imaging %>
    <%
    @ Import Namespace=System %>
    <%
    @ Import Namespace=System.Web %>
    <%
    @ Import Namespace=System.Runtime.InteropServices %>
    <%
    @ Import Namespace=System %>
    <%
    @ Import Namespace=System.IO %>
    <%

    Dim MStream As New MemoryStream()
    Dim full_thumb As New Bitmap(200, 200, PixelFormat.Format24bppRgb)
    Dim gr_square As Graphics = Graphics.FromImage(full_thumb)
    gr_square.FillRectangle(
    New SolidBrush(Color.FromArgb(255, 118, 118, 118)), 0, 0, 200, 200)
    Response.ContentType =
    "image/png"
    full_thumb.Save(MStream, ImageFormat.Png)
    MStream.WriteTo(Response.OutputStream)
    full_thumb.Dispose()
    gr_square.Dispose()

    %>

    Tuesday, September 4, 2007 3:19 PM

Answers

  • User-577306162 posted

    Then I'd prefer my solution :-)

    public static System.IO.Stream RemoveImageGamma(System.IO.Stream input)
    {
    	System.IO.Stream output = new System.IO.MemoryStream();
    	using (BinaryReader reader = new BinaryReader(input))
    	{
    		byte[] data = reader.ReadBytes((int)reader.BaseStream.Length);
    		int offset = FindOffset("gAMA", data);
    		if (offset > -1)
    		{
    			// The 'gAMA' chunk is 16 bytes long.
    			byte[] newData = new byte[data.Length - 16];
    
    			Array.Copy(data, 0, newData, 0, offset);
    			Array.Copy(data, offset + 16, newData, offset, data.Length - offset - 16);
    			output.Write(newData, 0, newData.Length);
    		}
    		else
    		{
    			output.Write(data, 0, data.Length);
    		}
    	}
    	return output;
    }
    
    private static int FindOffset(string chunkName, byte[] data)
    {
    	if (chunkName.Length == 4)
    		for (int i = 0; i < data.Length; i++)
    			if (data[i + 4] == chunkName[0] && data[i + 5] == chunkName[1] &&
    				data[i + 6] == chunkName[2] && data[i + 7] == chunkName[3])
    				return i;
    	return -1;
    }
    
     
    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Wednesday, September 5, 2007 7:33 PM

All replies

  • User-577306162 posted

    Hi,

    You should look for a chunk which is named "gAMA", this chunk should be removed from the png because IE interprets this and does some incorrect gamma correction based on it (which other browsers don't if remember correctly).

    Chunks are always preceeded with 4 bytes of data before it's name pops up. And last but not least the entire gAMA-chunk size is 16 bytes long, so you will have to remove that little bit from the stream you're writing out to the client.

    Hope it helps.

    -- Victor

    Wednesday, September 5, 2007 6:20 PM
  • User2015715426 posted

    Hi Victor,

    Thank you for the response and for the link to the structure. I also came up with the similar conclusion after doing some research. Here is a hack. May not be as elegant but it works so far (not thoroughly tested though). I suspect it might need more robust replacement routine and works only for the gamma value generated by default. More work is ahead..

    '.....

    full_thumb.Save(MemStream, ImageFormat.Png)
    Dim buffer As Byte() = MemStream.ToArray()
    Dim strBuffer As String = Encoding.Default.GetString(buffer)
    strBuffer = Replace(strBuffer, Chr(4) & Chr(103) & Chr(65) & Chr(77) & _
    Chr(65) & Chr(0) & Chr(0) & Chr(177) & Chr(143) & Chr(11) & Chr(252) & _
    Chr(97) & Chr(5) & Chr(0) & Chr(0) & Chr(0),
    "")
    buffer = Encoding.Default.GetBytes(strBuffer)
    Response.OutputStream.Write(buffer, 0, buffer.Length)

    '.....

    Wednesday, September 5, 2007 7:23 PM
  • User-577306162 posted

    Then I'd prefer my solution :-)

    public static System.IO.Stream RemoveImageGamma(System.IO.Stream input)
    {
    	System.IO.Stream output = new System.IO.MemoryStream();
    	using (BinaryReader reader = new BinaryReader(input))
    	{
    		byte[] data = reader.ReadBytes((int)reader.BaseStream.Length);
    		int offset = FindOffset("gAMA", data);
    		if (offset > -1)
    		{
    			// The 'gAMA' chunk is 16 bytes long.
    			byte[] newData = new byte[data.Length - 16];
    
    			Array.Copy(data, 0, newData, 0, offset);
    			Array.Copy(data, offset + 16, newData, offset, data.Length - offset - 16);
    			output.Write(newData, 0, newData.Length);
    		}
    		else
    		{
    			output.Write(data, 0, data.Length);
    		}
    	}
    	return output;
    }
    
    private static int FindOffset(string chunkName, byte[] data)
    {
    	if (chunkName.Length == 4)
    		for (int i = 0; i < data.Length; i++)
    			if (data[i + 4] == chunkName[0] && data[i + 5] == chunkName[1] &&
    				data[i + 6] == chunkName[2] && data[i + 7] == chunkName[3])
    				return i;
    	return -1;
    }
    
     
    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Wednesday, September 5, 2007 7:33 PM
  • User2015715426 posted
    That's excellent!. I have converted the routine into VB and it works like a charm. Thanks again. [Yes]<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p>
    Thursday, September 6, 2007 2:36 PM
  • User2095969710 posted

    I've got many errors with this code.

    At first it was something with offset.

    Then I dig up and found that  the following line

     
    byte[] data = reader.ReadBytes((int)reader.BaseStream.Length);
    
    
     
    does not read the data of input stream into the byte array.
    Can it be because my input stream is a MemoryStream?
     I've replaced the line with the following
      
    	byte[] data = new byte[(int)input.Length];
    	reader.Read(data, 0, data.Length);
      
     but then I got indexoutofrange error in FinOffset function
     I replaced 
     
    for (int i = 0; i < data.Length; i++)
      

     with

     

    for (int i = 0; i < data.Length-7; i++)

      

    but the code still does not work - the result image is currupt 

    Monday, May 26, 2008 9:14 AM
  • User2015715426 posted

    I had some problems too at the start while converting to VB. Here is what you can use with MemStream. Just pass your_mem_stream.ToArray()  into the function.

    using System;
    using System.IO;
    
    public class Gamma
    {
    
    	public static byte[] RemoveImageGamma(byte[] input)
    	{
    			int offset = FindOffset("gAMA", input);
    			if (offset > -1)
    			{
    				// The 'gAMA' chunk is 16 bytes long.
    				byte[] newData = new byte[input.Length - 16];
    	
    				Array.Copy(input, 0, newData, 0, offset);
    				Array.Copy(input, offset + 16, newData, offset, input.Length - offset - 16);
    				return newData;
    			}
    			else
    			{
    				return input;
    			}
    	}
    	
    	private static int FindOffset(string chunkName, byte[] data)
    	{
    		if (chunkName.Length == 4)
    			for (int i = 0; i < data.Length; i++)
    				if (data[i + 4] == chunkName[0] && data[i + 5] == chunkName[1] &&
    					data[i + 6] == chunkName[2] && data[i + 7] == chunkName[3])
    					return i;
    		return -1;
    	}
    
    }
     
    Monday, May 26, 2008 3:22 PM
  • User2095969710 posted

    Thanks! This one works! 

    Tuesday, May 27, 2008 4:38 AM
  • User-1827453801 posted

     This post just made my day. Thanks all.

    Wednesday, June 25, 2008 1:34 AM