Как правильно конвертировать из CMYK в RGB на Java? - PullRequest
21 голосов
/ 26 июня 2010

Мой Java-код для преобразования CMYK jpeg в RGB приводит к слишком легкому выходному изображению - см. Код ниже. Кто-нибудь может предложить правильный способ сделать преобразование?

Следующий код требует 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"));

    }
}

Ответы [ 6 ]

26 голосов
/ 26 августа 2012

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

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

Обновление:

Оригинальный код имеет небольшую проблему: результат был слишком ярким.Люди из проекта twelvemonkeys-imageio столкнулись с той же проблемой (см. post ) и исправили ее, исправив цветовой профиль так, что в Java используется перцептивная цветопередача.Исправление было интегрировано в приведенный выше код.

7 голосов
/ 03 октября 2011

Я скопирую свой ответ из другой ветки :

Для правильного отображения изображения CMYK должны содержать информацию о цветовом пространстве в качестве профиля ICC. Поэтому лучше всего использовать этот профиль ICC, который можно легко извлечь с помощью Sanselan :

ICC_Profile iccProfile = Sanselan.getICCProfile(new File("filename.jpg"));
ColorSpace cs = new ICC_ColorSpace(iccProfile);    

Если к изображению не прикреплен профиль ICC, я бы по умолчанию использовал Профили Adobe .

Теперь проблема в том, что вы не можете просто загрузить файл JPEG с пользовательским цветовым пространством, используя ImageIO, так как он не сможет выдать исключение, жалуясь на то, что он не поддерживает некоторое цветовое пространство или подобный стиль. Скорее всего, вам придется работать с растрами:

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

Затем вы можете использовать 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);
    }

Я почти уверен, что RasterOp или ColorConvertOp могут быть использованы для повышения эффективности разговора, но этого мне было достаточно.

Серьезно, нет необходимости использовать эти упрощенные алгоритмы преобразования CMYK в RGB, так как вы можете использовать профиль ICC, который встроен в изображение или доступен бесплатно от Adobe. Результирующее изображение будет выглядеть лучше, если не идеально (со встроенным профилем).

4 голосов
/ 09 июля 2014

Существует новая библиотека с открытым исходным кодом, которая поддерживает обработку CMYK. Все, что вам нужно сделать, это добавить зависимость в ваш проект, и новый читатель будет добавлен в список читателей (в то время как известный JPEGImageReader не может иметь дело с CMYK). Возможно, вы захотите перебрать эти читатели и прочитать изображение, используя первый читатель, который не выбрасывает исключение. Этот пакет является кандидатом на релиз, но я использую его, и он решил огромную проблему, с которой нам было трудно иметь дело.

http://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-jpeg/

РЕДАКТИРОВАТЬ: как указано в комментариях, теперь вы также можете найти стабильную релиз, а не RC.

Вы можете выполнить итерацию таким образом, чтобы получить BufferedImage, а после этого все остальное легко (вы можете использовать любой существующий пакет преобразования изображений, чтобы сохранить его в другом формате):

try (ImageInputStream input = ImageIO.createImageInputStream(source)) {

        // Find potential readers
        Iterator<ImageReader> readers = ImageIO.getImageReaders(input);

        // For each reader: try to read
        while (readers != null && readers.hasNext()) {
            ImageReader reader = readers.next();
            try {
                reader.setInput(input);
                BufferedImage image = reader.read(0);
                return image;
            } catch (IIOException e) {
                // Try next reader, ignore.
            } catch (Exception e) {
                // Unexpected exception. do not continue
                throw e;
            } finally {
                // Close reader resources
                reader.dispose();
            }
        }

        // Couldn't resize with any of the readers
        throw new IIOException("Unable to resize image");
    }
3 голосов
/ 03 августа 2012

Мое решение основано на предыдущем ответе.Я использовал "USWebCoatedSWOP.icc":

        //load source image
        JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(srcImageInputStream);
        BufferedImage src = decoder.decodeAsBufferedImage();
        WritableRaster srcRaster = src.getRaster();
        //prepare result image
        BufferedImage result = new BufferedImage(srcRaster.getWidth(), srcRaster.getHeight(), BufferedImage.TYPE_INT_RGB);
        WritableRaster resultRaster = result.getRaster();
        //prepare icc profiles
        ICC_Profile iccProfileCYMK = ICC_Profile.getInstance(new FileInputStream("path_to_cmyk_icc_profile"));
        ColorSpace sRGBColorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);

        //invert k channel
        for (int x = srcRaster.getMinX(); x < srcRaster.getWidth(); x++) {
            for (int y = srcRaster.getMinY(); y < srcRaster.getHeight(); y++) {
                float[] pixel = srcRaster.getPixel(x, y, (float[])null);
                pixel[3] = 255f-pixel[3];
                srcRaster.setPixel(x, y, pixel);
            }
        }

        //convert
        ColorConvertOp cmykToRgb = new ColorConvertOp(new ICC_ColorSpace(iccProfileCYMK), sRGBColorSpace, null);
        cmykToRgb.filter(srcRaster, resultRaster);

Другими словами:

  1. Открыть изображение как BufferedImage.
  2. Получить его растр.
  3. Инвертировать черный канал в этом растре.
  4. Преобразовать в rgb
1 голос
/ 26 июня 2010

CMYK в / из RGB сложно - вы конвертируете между аддитивным и вычитающим цветом. Если вы хотите точное соответствие, вам нужно изучить профили цветового пространства для каждого устройства. То, что выглядит нормально в одном цветовом пространстве, обычно не получается при физическом преобразовании в другое (т. Е. Правильный вывод CMYK, а не наивный предварительный просмотр на мониторе).

Исходя из моего собственного опыта, преобразование RGB в CMYK наивно приводит к получению слишком темного изображения. Учитывая, что вы сообщаете об обратном в обратном направлении, вероятно, существует приблизительная кривая регулировки яркости, которая будет работать неплохо (но не упустите странные нелинейности в цветовом пространстве). Если у вас есть доступ к Photoshop, я понимаю, что у него есть какая-то опция предварительного просмотра CMYK, которая может ускорить процесс определения такого приближения.

0 голосов
/ 08 июля 2016
    import java.awt.color.ColorSpace;
    import java.awt.color.ICC_ColorSpace;
    import java.awt.color.ICC_Profile;
    import java.io.IOException;
    import java.util.Arrays;


    public class ColorConv {
final static String pathToCMYKProfile = "C:\\UncoatedFOGRA29.icc";

public static float[] rgbToCmyk(float... rgb) throws IOException {
    if (rgb.length != 3) {
        throw new IllegalArgumentException();
    }
    ColorSpace instance = new ICC_ColorSpace(ICC_Profile.getInstance(pathToCMYKProfile));
    float[] fromRGB = instance.fromRGB(rgb);
    return fromRGB;
}
public static float[] cmykToRgb(float... cmyk) throws IOException {
    if (cmyk.length != 4) {
        throw new IllegalArgumentException();
    }
    ColorSpace instance = new ICC_ColorSpace(ICC_Profile.getInstance(pathToCMYKProfile));
    float[] fromRGB = instance.toRGB(cmyk);
    return fromRGB;
}

public static void main(String... args) {
    try {
        float[] rgbToCmyk = rgbToCmyk(1.0f, 1.0f, 1.0f);
        System.out.println(Arrays.toString(rgbToCmyk));
        System.out.println(Arrays.toString(cmykToRgb(rgbToCmyk[0], rgbToCmyk[1], rgbToCmyk[2], rgbToCmyk[3])));
    } catch (IOException e) {
        e.printStackTrace();
    }
}
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...