2011-03-07 14 views
2

ガンマ情報なしの画像を生成してIE8で正しく表示できるようにしようとしています。次のコードを使用しましたが、元画像のように見えない歪んだ画像になりました。ここPNGからガンマ情報を削除するには

///PNG 
    PNGEncodeParam params= PNGEncodeParam.getDefaultEncodeParam(outImage); 
    params.unsetGamma(); 
    params.setChromaticity(DEFAULT_CHROMA); 
    params.setSRGBIntent(PNGEncodeParam.INTENT_ABSOLUTE); 
    ImageEncoder encoder= ImageCodec.createImageEncoder("PNG", response.getOutputStream(), params); 
    encoder.encode(outImage); 
    response.getOutputStream().close(); 

original imageと、上記のコードに起因distorted oneあります。

ありがとうございます!

答えて

2

同じ質問がいくつかの場所で聞かれましたが、回答がないようですので、私はここに提供しています。 Javaのimageioがガンマを保存するかどうかはわかりません。ガンマがシステムに依存しているという事実を考えると、imageioはそれを扱うことはできないだろう。一つのことは確かです.pngを読むとき、imageioはガンマを無視します。

PNGはチャンクベースのイメージフォーマットです。ガンマは、さまざまなシステム上で「明るく」見えるようにイメージを作成するコンピュータシステムの違いを処理する14個の補助チャンクの1つです。各トランクは、データ長とトランクIDで始まり、その後に4バイトのCRCチェックサムが続きます。データ長には、データ長プロパティ自体とトランク識別子は含まれません。 gAMAチャンクは、16進数0x67414D41によって識別されます。

ここでは、pngイメージからgAMAを削除するための生の方法を示します。入力ストリームが有効なPNG形式であると仮定します。まず、png識別子0x89504e470d0a1a0aLである8バイトを読み込みます。次に、イメージヘッダーを構成する別の25バイトを読み込みます。全体で、ファイルの先頭から33バイトを読み込んでいます。今すぐそれらをpng拡張子を持つ別の一時ファイルに保存します。今はwhileループになります。チャンクを1つずつ読み込みます:もしそれがIENDでなく、それがgAMAのチャンクでなければ、それを出力tempfileにコピーします。 gAMAトランクの場合は、最後のチャンクであるIENDに達するまでスキップして、それを一時ファイルにコピーします。完了しました。ここでは、物事が行われる方法を示すために全体のテストコードは(それだけで最適化されていないデモ目的のためである)である:

import java.io.*; 

public class RemoveGamma 
{ 
    /** PNG signature constant */ 
    public static final long SIGNATURE = 0x89504E470D0A1A0AL; 
    /** PNG Chunk type constants, 4 Critical chunks */ 
    /** Image header */ 
    private static final int IHDR = 0x49484452; // "IHDR" 
    /** Image data */ 
    private static final int IDAT = 0x49444154; // "IDAT" 
    /** Image trailer */ 
    private static final int IEND = 0x49454E44; // "IEND" 
    /** Palette */ 
    private static final int PLTE = 0x504C5445; // "PLTE" 
    /** 14 Ancillary chunks */ 
    /** Transparency */ 
    private static final int tRNS = 0x74524E53; // "tRNs" 
    /** Image gamma */ 
    private static final int gAMA = 0x67414D41; // "gAMA" 
    /** Primary chromaticities */ 
    private static final int cHRM = 0x6348524D; // "cHRM" 
    /** Standard RGB color space */ 
    private static final int sRGB = 0x73524742; // "sRGB" 
    /** Embedded ICC profile */ 
    private static final int iCCP = 0x69434350; // "iCCP" 
    /** Textual data */ 
    private static final int tEXt = 0x74455874; // "tEXt" 
    /** Compressed textual data */ 
    private static final int zTXt = 0x7A545874; // "zTXt" 
    /** International textual data */ 
    private static final int iTXt = 0x69545874; // "iTXt" 
    /** Background color */ 
    private static final int bKGD = 0x624B4744; // "bKGD" 
    /** Physical pixel dimensions */ 
    private static final int pHYs = 0x70485973; // "pHYs" 
    /** Significant bits */ 
    private static final int sBIT = 0x73424954; // "sBIT" 
    /** Suggested palette */ 
    private static final int sPLT = 0x73504C54; // "sPLT" 
    /** Palette histogram */ 
    private static final int hIST = 0x68495354; // "hIST" 
    /** Image last-modification time */ 
    private static final int tIME = 0x74494D45; // "tIME" 

    public void remove(InputStream is) throws Exception 
    { 
     //Local variables for reading chunks 
      int data_len = 0; 
      int chunk_type = 0; 
      long CRC = 0; 
      byte[] buf=null; 

      DataOutputStream ds = new DataOutputStream(new FileOutputStream("temp.png")); 

      long signature = readLong(is); 

      if (signature != SIGNATURE) 
      { 
       System.out.println("--- NOT A PNG IMAGE ---"); 
       return; 
      } 

      ds.writeLong(SIGNATURE); 

      //******************************* 
      //Chuncks follow, start with IHDR 
      //******************************* 
      /** Chunk layout 
       Each chunk consists of four parts: 

       Length 
       A 4-byte unsigned integer giving the number of bytes in the chunk's data field. 
       The length counts only the data field, not itself, the chunk type code, or the CRC. 
       Zero is a valid length. Although encoders and decoders should treat the length as unsigned, 
       its value must not exceed 2^31-1 bytes. 

       Chunk Type 
       A 4-byte chunk type code. For convenience in description and in examining PNG files, 
       type codes are restricted to consist of uppercase and lowercase ASCII letters 
       (A-Z and a-z, or 65-90 and 97-122 decimal). However, encoders and decoders must treat 
       the codes as fixed binary values, not character strings. For example, it would not be 
       correct to represent the type code IDAT by the EBCDIC equivalents of those letters. 
       Additional naming conventions for chunk types are discussed in the next section. 

       Chunk Data 
       The data bytes appropriate to the chunk type, if any. This field can be of zero length. 

       CRC 
       A 4-byte CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, 
       including the chunk type code and chunk data fields, but not including the length field. 
       The CRC is always present, even for chunks containing no data. See CRC algorithm. 
      */ 

      /** Read header */ 
      /** We are expecting IHDR */ 
      if ((readInt(is)!=13)||(readInt(is) != IHDR)) 
      { 
       System.out.println("--- NOT A PNG IMAGE ---"); 
       return; 
      } 

      ds.writeInt(13);//We expect length to be 13 bytes 
      ds.writeInt(IHDR); 

      buf = new byte[13+4];//13 plus 4 bytes CRC 
      is.read(buf,0,17); 
      ds.write(buf); 

      while (true) 
      { 
       data_len = readInt(is); 
       chunk_type = readInt(is); 
       //System.out.println("chunk type: 0x"+Integer.toHexString(chunk_type)); 

       if (chunk_type == IEND) 
       { 
        System.out.println("IEND found"); 
        ds.writeInt(data_len); 
        ds.writeInt(IEND); 
        int crc = readInt(is); 
        ds.writeInt(crc); 
        break; 
       } 

       switch (chunk_type) 
       { 
        case gAMA://or any non-significant chunk you want to remove 
        { 
         System.out.println("gamma found"); 
         is.skip(data_len+4); 
         break; 
        } 
        default: 
        { 
         buf = new byte[data_len+4]; 
         is.read(buf,0, data_len+4); 
         ds.writeInt(data_len); 
         ds.writeInt(chunk_type); 
         ds.write(buf); 
         break; 
        } 
       } 
      } 
      is.close(); 
      ds.close(); 
    } 

    private int readInt(InputStream is) throws Exception 
    { 
     byte[] buf = new byte[4]; 
     is.read(buf,0,4); 
     return (((buf[0]&0xff)<<24)|((buf[1]&0xff)<<16)| 
           ((buf[2]&0xff)<<8)|(buf[3]&0xff)); 
    } 

    private long readLong(InputStream is) throws Exception 
    { 
     byte[] buf = new byte[8]; 
     is.read(buf,0,8); 
     return (((buf[0]&0xffL)<<56)|((buf[1]&0xffL)<<48)| 
           ((buf[2]&0xffL)<<40)|((buf[3]&0xffL)<<32)|((buf[4]&0xffL)<<24)| 
            ((buf[5]&0xffL)<<16)|((buf[6]&0xffL)<<8)|(buf[7]&0xffL)); 
    } 

    public static void main(String args[]) throws Exception 
    { 
     FileInputStream fs = new FileInputStream(args[0]); 
     RemoveGamma rg = new RemoveGamma(); 
     rg.remove(fs);  
    } 
} 

入力は、JavaのInputStreamであるので、私たちのように画像をエンコードするためのエンコーダのいくつかの種類を使用することができますPNGを作成し、ByteArrayOutputStreamに書き込みます。これは、後で上記のテストクラスにByteArrayInputSteamとして供給され、ガンマ情報があればそれが削除されます。ここでの結果である:

enter image description here

左側にgAMAと原画像であり、右側が除去にgAMAと同じ画像です。

画像ソース:http://r6.ca/cs488/kosh.png

編集:ここは、任意の補助チャンクを削除するコードの改訂版です。

import java.io.*; 
import java.util.HashMap; 
import java.util.HashSet; 
import java.util.Set; 

public class PNGChunkRemover 
{ 
    /** PNG signature constant */ 
    private static final long SIGNATURE = 0x89504E470D0A1A0AL; 
    /** PNG Chunk type constants, 4 Critical chunks */ 
    /** Image header */ 
    private static final int IHDR = 0x49484452; // "IHDR" 
    /** Image data */ 
    private static final int IDAT = 0x49444154; // "IDAT" 
    /** Image trailer */ 
    private static final int IEND = 0x49454E44; // "IEND" 
    /** Palette */ 
    private static final int PLTE = 0x504C5445; // "PLTE" 

    //Ancillary chunks keys 
    private static String[] KEYS = { "TRNS", "GAMA","CHRM","SRGB","ICCP","TEXT","ZTXT", 
             "ITXT","BKGD","PHYS","SBIT","SPLT","HIST","TIME"}; 

    private static int[] VALUES = {0x74524E53,0x67414D41,0x6348524D,0x73524742,0x69434350,0x74455874,0x7A545874, 
            0x69545874,0x624B4744,0x70485973,0x73424954,0x73504C54,0x68495354,0x74494D45}; 

    private static HashMap<String, Integer> TRUNK_TYPES = new HashMap<String, Integer>() 
    {{ 
     for(int i=0;i<KEYS.length;i++) 
      put(KEYS[i],VALUES[i]); 
    }}; 

    private static HashMap<Integer, String> REVERSE_TRUNK_TYPES = new HashMap<Integer,String>() 
    {{ 
     for(int i=0;i<KEYS.length;i++) 
      put(VALUES[i],KEYS[i]); 
    }}; 

    private static Set<Integer> REMOVABLE = new HashSet<Integer>(); 

    private static void remove(InputStream is, File dir, String fileName) throws Exception 
    { 
     //Local variables for reading chunks 
      int data_len = 0; 
      int chunk_type = 0; 
      byte[] buf=null; 

      DataOutputStream ds = new DataOutputStream(new FileOutputStream(new File(dir,fileName))); 

      long signature = readLong(is); 

      if (signature != SIGNATURE) 
      { 
       System.out.println("--- NOT A PNG IMAGE ---"); 
       return; 
      } 

      ds.writeLong(SIGNATURE); 

      /** Read header */ 
      /** We are expecting IHDR */ 
      if ((readInt(is)!=13)||(readInt(is) != IHDR)) 
      { 
       System.out.println("--- NOT A PNG IMAGE ---"); 
       return; 
      } 

      ds.writeInt(13);//We expect length to be 13 bytes 
      ds.writeInt(IHDR); 

      buf = new byte[13+4];//13 plus 4 bytes CRC 
      is.read(buf,0,17); 
      ds.write(buf); 

      while (true) 
      { 
       data_len = readInt(is); 
       chunk_type = readInt(is); 
       //System.out.println("chunk type: 0x"+Integer.toHexString(chunk_type)); 

       if (chunk_type == IEND) 
       { 
        System.out.println("IEND found"); 
        ds.writeInt(data_len); 
        ds.writeInt(IEND); 
        int crc = readInt(is); 
        ds.writeInt(crc); 
        break; 
       } 
       if(REMOVABLE.contains(chunk_type)) 
       { 
        System.out.println(REVERSE_TRUNK_TYPES.get(chunk_type)+"Chunk removed!"); 
        is.skip(data_len+4); 
       } 
       else 
       { 
        buf = new byte[data_len+4]; 
        is.read(buf,0, data_len+4); 
        ds.writeInt(data_len); 
        ds.writeInt(chunk_type); 
        ds.write(buf); 
       } 
      } 
      is.close(); 
      ds.close(); 
    } 

    private static int readInt(InputStream is) throws Exception 
    { 
     byte[] buf = new byte[4]; 
     int bytes_read = is.read(buf,0,4); 
     if(bytes_read<0) return IEND; 
     return (((buf[0]&0xff)<<24)|((buf[1]&0xff)<<16)| 
           ((buf[2]&0xff)<<8)|(buf[3]&0xff)); 
    } 

    private static long readLong(InputStream is) throws Exception 
    { 
     byte[] buf = new byte[8]; 
     int bytes_read = is.read(buf,0,8); 
     if(bytes_read<0) return IEND; 
     return (((buf[0]&0xffL)<<56)|((buf[1]&0xffL)<<48)| 
           ((buf[2]&0xffL)<<40)|((buf[3]&0xffL)<<32)|((buf[4]&0xffL)<<24)| 
            ((buf[5]&0xffL)<<16)|((buf[6]&0xffL)<<8)|(buf[7]&0xffL)); 
    } 

    public static void main(String args[]) throws Exception 
    { 
     if(args.length>0) 
     { 
      File[] files = {new File(args[0])}; 
      File dir = new File("."); 

      if(files[0].isDirectory()) 
      { 
      dir = files[0]; 

      files = files[0].listFiles(new FileFilter(){ 
       public boolean accept(File file) 
       { 
        if(file.getName().toLowerCase().endsWith("png")){ 
         return true; 
        } 
        return false; 
       } 
      } 
      ); 
      }  

      if(args.length>1) 
      { 
      FileInputStream fs = null; 

      if(args[1].equalsIgnoreCase("all")){ 
       REMOVABLE = REVERSE_TRUNK_TYPES.keySet(); 
      } 
      else 
      { 
       String key = ""; 
       for (int i=1;i<args.length;i++) 
       { 
        key = args[i].toUpperCase(); 
        if(TRUNK_TYPES.containsKey(key)) 
         REMOVABLE.add(TRUNK_TYPES.get(key)); 
       } 
      } 
      for(int i= files.length-1;i>=0;i--) 
      { 
       String outFileName = files[i].getName(); 
       outFileName = outFileName.substring(0,outFileName.lastIndexOf('.')) 
        +"_slim.png"; 
       System.out.println("<<"+files[i].getName()); 
       fs = new FileInputStream(files[i]); 
       remove(fs, dir, outFileName); 
       System.out.println(">>"+outFileName); 
       System.out.println("************************"); 
      } 
      } 
     } 
    } 
} 

使用上:java PNGChunkRemover filename.png allが事前定義された14件の補助チャンクのいずれかが削除されます。

java PNGChunkRemover filename.png gama time ...は、pngファイルの後に指定されたチャンクのみを削除します。

注: PNGChunkRemoverの最初の引数としてフォルダ名が指定されている場合、そのフォルダ内のすべてのpngファイルが処理されます。

上記の例では、https://github.com/dragon66/icafe

0

で見つけることができるのJava画像ライブラリの一部となっています。また、(私の)PNGJライブラリ http://code.google.com/p/pngj/

でそれを行うことができます例:

PngReader pngr = FileHelper.createPngReader(new File(origFilename)); 
PngWriter pngw = FileHelper.createPngWriter(new File(destFilename), pngr.imgInfo, false); 
pngw.copyChunksFirst(pngr, ChunkCopyBehaviour.COPY_ALL); // all chunks are queued 
PngChunkGAMA gama = (PngChunkGAMA) pngw.getChunkList().getQueuedById1(ChunkHelper.gAMA); 
if (gama != null) { 
    System.out.println("removing gama chunk gamma=" + gama.getGamma()); 
    pngw.getChunkList().removeChunk(gama); 
} 
for (int row = 0; row < pngr.imgInfo.rows; row++) { 
    ImageLine l1 = pngr.readRow(row); 
    pngw.writeRow(l1, row); 
} 
pngw.copyChunksLast(pngr, ChunkCopyBehaviour.COPY_ALL); // in case some new metadata has been read 
pngw.end(); 

ライブラリsamplesに含まれています。

関連する問題