我将用于将CMYK jpeg转换为RGB的Java代码导致输出图像太亮 - 请参阅下面的代码.任何人都可以建议正确的转换方式吗?
以下代码需要Java Advanced Image IO来读取jpeg和example-cmyk.jpg
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.io.File;
import javax.imageio.ImageIO;
public class TestCmykToRgb {
public static void main(String[] args) throws Exception {
BufferedImage cmykImage = ImageIO.read(new File(
"j:\\temp\\example-cmyk.jpg"));
BufferedImage rgbImage = new BufferedImage(cmykImage.getWidth(),
cmykImage.getHeight(), BufferedImage.TYPE_INT_RGB);
ColorConvertOp op = new ColorConvertOp(null);
op.filter(cmykImage, rgbImage);
ImageIO.write(rgbImage, "JPEG", new File("j:\\temp\\example-rgb.jpg"));
}
}
Run Code Online (Sandbox Code Playgroud)
Cod*_*odo 26
现有答案中已有很多好东西.但它们都不是处理不同类型的CMYK JPEG图像的完整解决方案.
对于CMYK JPEG图像,您需要区分常规CMYK,Adobe CMYK(具有反转值,即255表示无墨水,0表示最大墨水)和Adobe CYYK(某些变体具有反转颜色).
这里的解决方案需要Sanselan(或现在称为Apache Commons Imaging),它需要合理的CMYK颜色配置文件(.icc文件).您可以从Adobe或eci.org获取后者.
public class JpegReader {
public static final int COLOR_TYPE_RGB = 1;
public static final int COLOR_TYPE_CMYK = 2;
public static final int COLOR_TYPE_YCCK = 3;
private int colorType = COLOR_TYPE_RGB;
private boolean hasAdobeMarker = false;
public BufferedImage readImage(File file) throws IOException, ImageReadException {
colorType = COLOR_TYPE_RGB;
hasAdobeMarker = false;
ImageInputStream stream = ImageIO.createImageInputStream(file);
Iterator<ImageReader> iter = ImageIO.getImageReaders(stream);
while (iter.hasNext()) {
ImageReader reader = iter.next();
reader.setInput(stream);
BufferedImage image;
ICC_Profile profile = null;
try {
image = reader.read(0);
} catch (IIOException e) {
colorType = COLOR_TYPE_CMYK;
checkAdobeMarker(file);
profile = Sanselan.getICCProfile(file);
WritableRaster raster = (WritableRaster) reader.readRaster(0, null);
if (colorType == COLOR_TYPE_YCCK)
convertYcckToCmyk(raster);
if (hasAdobeMarker)
convertInvertedColors(raster);
image = convertCmykToRgb(raster, profile);
}
return image;
}
return null;
}
public void checkAdobeMarker(File file) throws IOException, ImageReadException {
JpegImageParser parser = new JpegImageParser();
ByteSource byteSource = new ByteSourceFile(file);
@SuppressWarnings("rawtypes")
ArrayList segments = parser.readSegments(byteSource, new int[] { 0xffee }, true);
if (segments != null && segments.size() >= 1) {
UnknownSegment app14Segment = (UnknownSegment) segments.get(0);
byte[] data = app14Segment.bytes;
if (data.length >= 12 && data[0] == 'A' && data[1] == 'd' && data[2] == 'o' && data[3] == 'b' && data[4] == 'e')
{
hasAdobeMarker = true;
int transform = app14Segment.bytes[11] & 0xff;
if (transform == 2)
colorType = COLOR_TYPE_YCCK;
}
}
}
public static void convertYcckToCmyk(WritableRaster raster) {
int height = raster.getHeight();
int width = raster.getWidth();
int stride = width * 4;
int[] pixelRow = new int[stride];
for (int h = 0; h < height; h++) {
raster.getPixels(0, h, width, 1, pixelRow);
for (int x = 0; x < stride; x += 4) {
int y = pixelRow[x];
int cb = pixelRow[x + 1];
int cr = pixelRow[x + 2];
int c = (int) (y + 1.402 * cr - 178.956);
int m = (int) (y - 0.34414 * cb - 0.71414 * cr + 135.95984);
y = (int) (y + 1.772 * cb - 226.316);
if (c < 0) c = 0; else if (c > 255) c = 255;
if (m < 0) m = 0; else if (m > 255) m = 255;
if (y < 0) y = 0; else if (y > 255) y = 255;
pixelRow[x] = 255 - c;
pixelRow[x + 1] = 255 - m;
pixelRow[x + 2] = 255 - y;
}
raster.setPixels(0, h, width, 1, pixelRow);
}
}
public static void convertInvertedColors(WritableRaster raster) {
int height = raster.getHeight();
int width = raster.getWidth();
int stride = width * 4;
int[] pixelRow = new int[stride];
for (int h = 0; h < height; h++) {
raster.getPixels(0, h, width, 1, pixelRow);
for (int x = 0; x < stride; x++)
pixelRow[x] = 255 - pixelRow[x];
raster.setPixels(0, h, width, 1, pixelRow);
}
}
public static BufferedImage convertCmykToRgb(Raster cmykRaster, ICC_Profile cmykProfile) throws IOException {
if (cmykProfile == null)
cmykProfile = ICC_Profile.getInstance(JpegReader.class.getResourceAsStream("/ISOcoated_v2_300_eci.icc"));
if (cmykProfile.getProfileClass() != ICC_Profile.CLASS_DISPLAY) {
byte[] profileData = cmykProfile.getData();
if (profileData[ICC_Profile.icHdrRenderingIntent] == ICC_Profile.icPerceptual) {
intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first
cmykProfile = ICC_Profile.getInstance(profileData);
}
}
ICC_ColorSpace cmykCS = new ICC_ColorSpace(cmykProfile);
BufferedImage rgbImage = new BufferedImage(cmykRaster.getWidth(), cmykRaster.getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster rgbRaster = rgbImage.getRaster();
ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace();
ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null);
cmykToRgb.filter(cmykRaster, rgbRaster);
return rgbImage;
}
}
static void intToBigEndian(int value, byte[] array, int index) {
array[index] = (byte) (value >> 24);
array[index+1] = (byte) (value >> 16);
array[index+2] = (byte) (value >> 8);
array[index+3] = (byte) (value);
}
Run Code Online (Sandbox Code Playgroud)
代码首先尝试使用常规方法读取文件,该方法适用于RGB文件.如果失败,则会读取颜色模型的详细信息(配置文件,Adobe标记,Adobe变体).然后它读取原始像素数据(光栅)并进行所有必要的转换(YCCK到CMYK,反转颜色,CMYK到RGB).
更新:
原始代码有一个小问题:结果太亮了.来自12monmonkey-imageio项目的人们遇到了同样的问题(参见这篇文章)并通过修补颜色配置文件修复了它,以便Java使用感知颜色渲染意图.该修复程序已集成到上面的代码中.
小智 7
我将从另一个帖子中复制我的答案:
为了正确显示,CMYK图像应包含颜色空间信息作为ICC配置文件.因此,最好的方法是使用可以使用Sanselan轻松提取的ICC配置文件:
ICC_Profile iccProfile = Sanselan.getICCProfile(new File("filename.jpg"));
ColorSpace cs = new ICC_ColorSpace(iccProfile);
Run Code Online (Sandbox Code Playgroud)
如果图像上没有附加ICC配置文件,我会使用Adobe配置文件作为默认配置文件.
现在的问题是你不能只使用ImageIO加载带有自定义颜色空间的JPEG文件,因为它会失败抛出异常,抱怨它不支持某些颜色空间或像这样的sthing.Hense你将不得不使用栅格:
JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(new ByteArrayInputStream(data));
Raster srcRaster = decoder.decodeAsRaster();
BufferedImage result = new BufferedImage(srcRaster.getWidth(), srcRaster.getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster resultRaster = result.getRaster();
ColorConvertOp cmykToRgb = new ColorConvertOp(cs, result.getColorModel().getColorSpace(), null);
cmykToRgb.filter(srcRaster, resultRaster);
Run Code Online (Sandbox Code Playgroud)
然后,您可以result在任何需要的地方使用,它将转换颜色.
然而,在实践中,我遇到了一些图像(用相机拍摄并用Photoshop处理过),这些图像以某种方式反转了颜色值,因此生成的图像总是被反转,即使再次反转它们也太亮了.虽然我仍然不知道如何确定何时使用它(当我需要反转像素值时),我有一个算法可以校正这些值并逐个像素地转换颜色:
JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(new ByteArrayInputStream(data));
Raster srcRaster = decoder.decodeAsRaster();
BufferedImage ret = new BufferedImage(srcRaster.getWidth(), srcRaster.getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster resultRaster = ret.getRaster();
for (int x = srcRaster.getMinX(); x < srcRaster.getWidth(); ++x)
for (int y = srcRaster.getMinY(); y < srcRaster.getHeight(); ++y) {
float[] p = srcRaster.getPixel(x, y, (float[])null);
for (int i = 0; i < p.length; ++i)
p[i] = 1 - p[i] / 255f;
p = cs.toRGB(p);
for (int i = 0; i < p.length; ++i)
p[i] = p[i] * 255f;
resultRaster.setPixel(x, y, p);
}
Run Code Online (Sandbox Code Playgroud)
我很确定RasterOp或ColorConvertOp可以用来提高对话效率,但这对我来说已经足够了.
说真的,没有必要使用这些简化的CMYK到RGB转换算法,因为您可以使用嵌入到图像中或可从Adobe免费获得的ICC配置文件.如果不完美(使用嵌入的配置文件),所得到的图像将看起来更好.
CMYK 与 RGB 之间的转换很困难 - 您需要在加色和减色之间进行转换。如果您想要精确匹配,则需要查看每个设备的色彩空间配置文件。在一种色彩空间中看起来不错的内容,在物理上转换为另一种色彩空间时通常会出现问题(即正确的 CMYK 输出 - 不是在显示器上进行简单的预览)。
根据我自己的经验,简单地将 RGB 转换为 CMYK 往往会导致图像太暗。鉴于您在相反的方向上报告相反的情况,可能会找到一条近似的亮度调整曲线,这将起到不错的作用(但要注意颜色空间内奇怪的非线性)。如果您可以使用 Photoshop,我知道它有某种 CMYK 预览选项,这可能会加快计算近似值的过程。
| 归档时间: |
|
| 查看次数: |
28182 次 |
| 最近记录: |