同じ質問がいくつかの場所で尋ねられたのを見ましたが、答えがないようですので、ここで私のものを提供しています。Javaimageioがガンマを保存するかどうかはわかりません。ガンマがシステムに依存しているという事実を考えると、imageioがそれを処理できる可能性は低いです。1つ確かなことは、imageioはpngを読み取るときにガンマを無視するということです。
PNGはチャンクベースの画像形式です。ガンマは、画像を作成するコンピューターシステムの違いを処理して、さまざまなシステムでほぼ同じように「明るく」見えるようにする14の補助チャンクの1つです。各トランクは、データ長とトランクIDで始まり、その後に4バイトのCRCチェックサムが続きます。データ長には、データ長プロパティ自体とトランク識別子は含まれません。gAMAチャンクはhexで識別され0x67414D41
ます。
png画像からgAMAを削除する生の方法は次のとおりです。入力ストリームが有効なPNG形式であると想定しています。最初に、png識別子である8バイトを読み取ります0x89504e470d0a1a0aL
。次に、画像ヘッダーを構成する別の25バイトを読み取ります。全体で、ファイルの先頭から33バイトを読み取りました。次に、それらをpng拡張子の付いた別の一時ファイルに保存します。今度はwhileループになります。チャンクを1つずつ読み取ります。IENDでもGAMAチャンクでもない場合は、出力一時ファイルにコピーします。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);
}
}
入力はJavaInputStreamであるため、ある種のエンコーダーを使用して画像をPNGとしてエンコードし、それをByteArrayOutputStreamに書き込むことができます。これは、後でByteArrayInputSteamとして上記のテストクラスに送られ、ガンマ情報(存在する場合)は除去される。結果は次のとおりです。

左側は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にあるJavaイメージライブラリの一部になっています。