Подтвердить что ты не робот

Как правильно конвертировать из CMYK в RGB в Java?

Мой 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"));

    }
}
4b9b3361

Ответ 1

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

Update:

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

Ответ 2

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

Чтобы правильно отображаться, изображения 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. Полученное изображение будет выглядеть лучше, если не идеально (со встроенным профилем).

Ответ 3

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

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

EDIT: как указано в комментариях, вы также можете найти стабильную релиз, а не 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");
    }

Ответ 4

Мое решение основано на предыдущем ответе. Я использовал "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);

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

  • Откройте изображение как BufferedImage.
  • Получить растровый файл.
  • Инвертируйте черный канал в этом растере.
  • Преобразовать в rgb

Ответ 5

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

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

Ответ 6

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