gpt4 book ai didi

java - 如何从 PNG 中删除 Gamma 信息

转载 作者:塔克拉玛干 更新时间:2023-11-02 08:26:28 24 4
gpt4 key购买 nike

我正在尝试生成没有 Gamma 信息的图像,以便 IE8 可以正确显示它们。使用了以下代码,但结果是一张扭曲的图像,看起来与原始图像完全不同。

 ///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 imagedistorted one由上面的代码产生。

谢谢!

最佳答案

我看到同一个问题问了几个地方,但似乎没有答案,所以我在这里提供我的。我不知道 Java imageio 是否保存 Gamma 。鉴于 Gamma 依赖于系统这一事实,imageio 不太可能处理它。有一件事是肯定的:imageio 在读取 png 时忽略 gamma。

PNG 是一种基于 block 的图像格式。 Gamma 是 14 个辅助 block 之一,它处理创建图像的计算机系统的差异,使它们在不同系统上看起来或多或少同样“明亮”。每个中继以数据长度和中继标识符开始,后跟 4 字节的 CRC 校验和。数据长度不包括数据长度属性本身和中继标识符。 gAMA block 由十六进制 0x67414D41 标识。

这是从 png 图像中删除 gAMA 的原始方法:我们假设输入流是有效的 PNG 格式。首先读取 8 个字节,即 png 标识符 0x89504e470d0a1a0aL。然后读取另外 25 个字节,其中包含图像头。我们总共从文件顶部读取了 33 个字节。现在将它们保存到另一个扩展名为 png 的临时文件中。现在涉及到 while 循环。我们一个一个地读取 block :如果它不是 IEND 也不是 gAMA block ,我们将它复制到输出临时文件。如果它是一个 gAMA 主干,我们跳过它,直到我们到达 IEND,这应该是最后一个 block ,我们将它复制到临时文件。完毕。这是完整的测试代码,展示了事情是如何完成的(它只是为了演示目的,没有优化):

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 和 Gamma 信息(如果任何)将被删除。这是结果:

enter image description here

左边是加了 gAMA 的原图,右边是去掉 gAMA 后的原图。

图片来源:http://r6.ca/cs488/kosh.png

编辑:这里是代码的修订版本,用于删除任何辅助 block 。

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 个辅助 block 。

java PNGChunkRemover filename.png gama time ... 将仅删除 png 文件之后指定的 block 。

注意:如果将文件夹名称指定为 PNGChunkRemover 的第一个参数,则将处理该文件夹中的所有 png 文件。

上面的例子已经成为 Java 图像库的一部分,可以在 https://github.com/dragon66/icafe 找到。

关于java - 如何从 PNG 中删除 Gamma 信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5223340/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com