Java CMYK в RGB с профилем. Выход слишком темный

Подобный вопрос задавался много раз. Но я до сих пор не понимаю, почему у меня получается слишком темное изображение после того, как я преобразовал изображение с помощью ICC_Profile. Я перепробовал много профилей: с сайта Adobe и с самой картинки.

Перед изображением

Перед изображением

После изображения

После изображения

Код

Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpeg");
ImageReader reader = null;
while (readers.hasNext()){
      reader = readers.next();
      if (reader.canReadRaster()){
          break;
      }
}
// read
ImageInputStream ios = ImageIO.createImageInputStream(new FileInputStream(new File(myPic.jpg)));
reader.setInput(ios);
Raster r = reader.readRaster(0, null);

BufferedImage result = new BufferedImage(r.getWidth(), r.getHeight(), bufferedImage.TYPE_INT_RGB);
WritableRaster resultRaster = result.getRaster();
ICC_Profile iccProfile = ICC_Profile.getInstance(new File("profile_name.icc");
ColorSpace cs = new ICC_ColorSpace(iccProfile);
ColorConvertOp cmykToRgb = new ColorConvertOp(cs, result.getColorModel().getColorSpace(), null);
cmykToRgb.filter(r, resultRaster);

// write
ImageIo.write(resul, "jpg", new File("myPic.jpg"));

Должен ли я сделать что-то еще после того, как я преобразовал изображение?

4 ответа

Решение

Как я уже сказал, идея состояла в том, чтобы преобразовать изображения CMYK в RGB и использовать их в моем приложении.

Но по какой-то причине ConvertOp не выполняет преобразование CMYK в RGB. Уменьшает число numBand до 3 и все. И я решил попробовать алгоритмы CMYK to RGB.

т.е. получить изображение, распознать его ColorSpace и прочитать его или преобразовать его.

Также еще одной проблемой был фотошоп. Эту цитату я нашел в интернете.

В случае Adobe он включает профиль CMYK в метаданные, но затем сохраняет необработанные данные изображения в виде инвертированных цветов YCbCrK.

Наконец я смог достичь своей цели с помощью этого алгоритма ниже. Пока я не использую icc_profiles, результат выглядит немного темнее... Я получил правильные изображения RGB, которые выглядят нормально.

псевдокод

BufferedImage result = null;
Raster r = reader.readRaster()
if (r.getNumBands != 4){
    result = reader.read(0);
} else {

   if (isPhotoshopYCCK(reader)){
       result = YCCKtoCMYKtoRGB(r);
   }else{
      result = CMYKtoRGB(r);
   }
}

private boolean isPhotoshopYCCK(reader){
    // read IIOMetadata from reader and according to
    // http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html decide which ColorSpace is used
    // or maybe there is another way to do it
    int transform = ... // 2 or 0 or something else
    return transform;
}    

Я не имею никакого смысла показывать алгоритмы YCCKtoCMYKtoRGB или CMYK to RGB. Это легко найти в интернете.

Большое спасибо за вашу помощь, ребята.

Этот вопрос не совсем новый. Но так как я потратил много времени на проблему и нашел рабочее решение, я решил опубликовать его здесь. Для этого решения требуется Sanselan (или Apache Commons Imaging, как его сейчас называют) и разумный цветовой профиль CMYK (файл.icc). Вы можете получить более позднюю версию из Adobe или eci.org.

Основная проблема заключается в том, что Java - из коробки - может читать файлы JPEG только в RGB. Если у вас есть файл CMYK, вам необходимо различать обычный CMYK, Adobe CMYK (с инвертированными значениями, т.е. 255 для отсутствия чернил и 0 для максимального объема чернил) и Adobe CYYK (в некоторых вариантах также с инвертированными цветами).

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"));
        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;
    }
}

Сначала код пытается прочитать файл, используя обычный метод, который работает для файлов RGB. Если это не удается, он считывает детали цветовой модели (профиль, маркер Adobe, вариант Adobe). Затем он считывает необработанные данные пикселей (растр) и выполняет все необходимые преобразования (YCCK в CMYK, инвертированные цвета, CMYK в RGB).

Я не совсем доволен своим решением. В то время как цвета в основном хорошие, темные области немного слишком яркие, в частности черный не полностью черный. Если кто-нибудь знает, что я могу улучшить, я был бы рад услышать это.

Обновить:

Я разобрался, как исправить проблемы с яркостью. Или, скорее: люди из проекта twelvemonkeys-imageio (см. Этот пост). Это связано с целью цветопередачи.

Исправление заключается в добавлении следующих строк, которые хорошо работают для меня. В основном, цветовой профиль изменен, потому что, кажется, нет другого способа сказать ColorConvertOp класс для использования восприятия цветопередачи

    if (cmykProfile.getProfileClass() != ICC_Profile.CLASS_DISPLAY) {
        byte[] profileData = cmykProfile.getData(); // Need to clone entire profile, due to a JDK 7 bug

        if (profileData[ICC_Profile.icHdrRenderingIntent] == ICC_Profile.icPerceptual) {
            intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first

            cmykProfile = ICC_Profile.getInstance(profileData);
        }
    }

...

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);
}
  1. Вы пишете результат, заданный в CMYK, как Jpeg, т.е. как если бы он был записан в формате RGB. Итак, ошибка, на мой взгляд, НЕ в той части кода, в которой вы ее просматриваете:-)

  2. правильный результат, чтобы привести к последней строке.

Если у вас были одни и те же проблемы с двумя абсолютно разными способами с одним и тем же файлом профиля, я думаю, что файл (profile_name.icc) не в порядке.

Другие вопросы по тегам