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

#java #cmyk

#java #cmyk

Вопрос:

Подобный вопрос задавался много раз. Но я все еще не понимаю, почему я получаю слишком темный вывод после того, как я преобразовал изображение с помощью 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"));
  

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

Комментарии:

1. Извините, но ваш вопрос неполон. Откуда вы берете изображение, куда вы его помещаете, как выглядит файл profile_name.icc…

2. хм. Я получил это изображение от дизайнера. Он был создан с использованием профиля CMYK. Этот профиль встроен в само изображение. Я попробовал 2 способа: 1. загрузил список icc_profiles с сайта Adobe и использовал приведенный выше код; 2. извлек профиль изображения с помощью Sanselan и использовал приведенный выше код. Оба этих способа дают одинаковый результат, вы можете увидеть его здесь «до» и «после». Я надеюсь, что это прояснит проблему

3. Извините за некорректные вопросы. Я имею в виду не то место, где ВЫ предварительно получили изображение :-), а то, где ваш код его принимает. Это r? Как это читается? Где его определение? То же самое с выводом.

4. @nixspirit: Хотя вашему вопросу несколько месяцев, я опубликовал новый ответ, который объясняет большинство основных проблем и включает в себя рабочее решение. Темные цвета в основном из-за старой ошибки Photoshop (значения CMYK инвертированы), которая теперь стала стандартом defacto и обрабатывается большинством программ для работы с JPEG (кроме Java).

Ответ №1:

Этот вопрос не совсем новый. Но поскольку я потратил много времени на проблему и нашел рабочее решение, я решил опубликовать его здесь. Для решения требуется 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 amp;amp; segments.size() >= 1) {
            UnknownSegment app14Segment = (UnknownSegment) segments.get(0);
            byte[] data = app14Segment.bytes;
            if (data.length >= 12 amp;amp; data[0] == 'A' amp;amp; data[1] == 'd' amp;amp; data[2] == 'o' amp;amp; data[3] == 'b' amp;amp; data[4] == 'e')
            {
                hasAdobeMarker = true;
                int transform = app14Segment.bytes[11] amp; 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 marker, Adobe variant). Затем он считывает необработанные пиксельные данные (растр) и выполняет все необходимые преобразования (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. Я попробовал ваш код с этим изображением: en.wikipedia.org/wiki/File:Channel_digital_image_CMYK_color.jpg Я использовал последнюю версию Sanselan без предоставления какого-либо цветового профиля (как я должен его предоставить?). Код работает, но результирующие цвета ярче исходных. Для меня этого недостаточно, чтобы перейти к производству. В любом случае, спасибо.

2. Я знаю об этом (см. Последний абзац моего ответа). Если вы выясните, в чем причина (возможно, у меня ошибка в коде), пожалуйста, дайте мне знать.

3. @Pino: Я обнаружил исправление проблемы с яркостью в сети. Смотрите мое обновление.

4. Интересно, но вы пропустили метод intToBigEndian().

5. Я добавил отсутствующий метод.

Ответ №2:

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

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

т. е. Получаем изображение, распознаем его цветовое пространство и считываем его или преобразуем.

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

В случае 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 или CMYKtoRGB.
Его легко найти в Интернете.

Комментарии:

1. Если это было ваше решение, можете ли вы опубликовать завершенный код?

Ответ №3:

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

Комментарии:

1. Я загрузил около 20 профилей из Adobe . Пробовал каждый из них, и ничего не изменилось.

2. Также я извлек профиль из самого изображения с помощью Sanselan . и создан таким образом ICC_Profile iccProfile = Sanselan.getICCProfile(byteArrayStreamFromFile); результат тот же

3. похоже, что этот код ColorConvertOp cmykToRgb = new ColorConvertOp(cs, result.getColorModel().getColorSpace(), null); cmykToRgb.filter(r, resultRaster); не преобразует этот растр из CMYK в цветовое пространство RGB.

Ответ №4:

  1. Вы записываете результат, приведенный в CMYK, в формате Jpeg, то есть так, как если бы он был записан в формате RGB. Итак, ошибка, как я вижу, не в том фрагменте кода, в котором вы ее просматриваете 🙂

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

Комментарии:

1. извините, я не могу отредактировать свой пост или мне придется удалить картинки. Я здесь новый пользователь, и некоторые модераторы добавили эти картинки в сообщение для меня

2. Но предполагается, что растр должен быть преобразован из CMYK в RGB с помощью ColorConvertOp cmykToRgb = new ColorConvertOp(cs, result.getColorModel().getColorSpace(), null); cmykToRgb.filter(r, resultRaster) , и результирующее изображение содержит этот результирующий растр

3. Я думаю, что вы преобразовали его нормально, но теперь, после преобразования, он записан в другом формате — CMYK, 4 цветовых координаты вместо 3. И вы не можете показать это как обычно. И вы не можете работать с ним, как с изображением в RPG. Итак, ваше преобразование в порядке. Но часть после него принадлежит совершенно другой задаче.

4. Я поставил 1 на ваш вопрос, так что у вас будет больше очков, и, надеюсь, скоро вы сможете редактировать. Кстати, вопрос действительно интересный — он появлялся раньше, ответ был таким, как вы указали в коде, но вы обнаружили дальнейшую проблему.

5. Но часть после него относится к другой задаче в целом согласовано. Когда я закончу с этим вопросом, я приведу полный ответ и некоторые пояснения