Comment convertir les photos CMYK en RVB en Java correctement?

mon code Java pour convertir un CMYK jpeg en RVB se traduit par une image de sortie beaucoup trop légère - voir code ci-dessous. Est-ce que quelqu'un peut suggérer la bonne façon de faire la conversion?

le code suivant exige Java Advanced Image IO pour lire le jpeg et exemple-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:tempexample-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:tempexample-rgb.jpg"));

    }
}
21
demandé sur Jonas 2010-06-26 14:46:59

6 réponses

Il y a beaucoup de bonnes choses dans les réponses déjà. Mais aucun D'entre eux n'est une solution complète qui gère les différents types D'images JPEG CMYK.

pour les images JPEG CMYK, vous devez faire la distinction entre le CMYK régulier, Adobe CMYK (avec des valeurs inversées, i.e. 255 pour pas d'encre et 0 pour un maximum d'encre) et Adobe CYYK (une variante avec des couleurs inversées aussi bien).

cette solution nécessite ici Sanselan (ou Apache Commons Imaging as il est appelé maintenant) et il nécessite un profil de couleur CMYK raisonnable (.fichier icc). Vous pouvez obtenir la dernière de Adobe ou de 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);
}

le code essaie d'abord de lire le fichier en utilisant la méthode régulière, qui fonctionne pour les fichiers RGB. S'il échoue, il lit les détails du modèle Couleur (profil, Adobe marker, variante Adobe). Puis il lit les données brutes de pixel (raster) et fait toute la conversion nécessaire (YCCK en CMYK, couleurs inversées, CMYK en RVB).

mise à jour:

Le code original a un léger problème: le résultat était trop lumineux. Les personnes du projet twelvemonkeys-imageio ont eu le même problème (voir ce post ) et l'ont corrigé en corrigeant le profil de couleur de telle sorte que Java utilise un rendu de couleur perceptuel intention. Le correctif a été intégré dans le code ci-dessus.

23
répondu Codo 2012-09-12 10:37:50

je vais copier ma réponse à partir du autre fil :

afin d'être affichées correctement, les images CMYK doivent contenir information sur l'espace de couleur comme Profil ICC. Donc la meilleure façon est d'utiliser ce profil ICC qui peut être facilement extrait avec Sanselan :

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

dans le cas où il n'y a pas de profil ICC attaché à l'image, j'utiliserais profils Adobe par défaut.

maintenant, le problème est que vous ne pouvez pas simplement charger le fichier JPEG avec l'espace de couleur personnalisé en utilisant ImageIO car il échouera en lançant une exception se plaindre qu'il ne supporte pas un certain espace de couleur ou la piqûre comme cela. Hense vous devrez travailler avec des rasoirs:

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

vous pouvez alors utiliser result où que vous ayez besoin et il aura des couleurs converties.

dans la pratique, cependant j'ai rencontré certains des images (prises avec caméra et traitées avec Photoshop) qui avaient en quelque sorte inversé les valeurs de couleur de sorte que l'image résultante a toujours été inversée et même après les inverser une fois de plus ils étaient trop lumineux. Bien que je ne sais toujours pas comment savoir quand exactement l'utiliser (quand j'ai besoin d'inverser les valeurs de pixel), j'ai un algorithme qui corrige ces valeurs et convertir pixel couleur par pixel:

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

Je suis assez sûr que RasterOp ou ColorConvertOp pourrait être utilisé pour faire conversation plus efficace, mais ça me suffisait.

sérieusement, il n'est pas nécessaire d'utiliser ces algorithmes de conversion CMJN simplifiés en RVB car vous pouvez utiliser le profil ICC qui est intégré dans l'image ou disponible gratuitement sur Adobe. Image résultante mieux si pas parfait (avec profil incorporé).

7
répondu Saulius 2017-05-23 12:32:20

il y a une nouvelle bibliothèque open source qui supporte le traitement CMYK. Tout ce que vous devez faire est d'ajouter la dépendance à votre projet et un nouveau lecteur sera ajouté à la liste des lecteurs (alors que le JPEGImageReader connu ne peut pas traiter avec CMYK). Vous voudrez probablement itérer sur ces lecteurs et lire l'image en utilisant le premier lecteur qui ne jette pas l'exception. Ce paquet est un candidat à la publication, mais je l'utilise et il a résolu un énorme problème que nous avons eu du mal à traiter.

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

EDIT: comme indiqué dans les commentaires, vous pouvez maintenant aussi trouver une stable libération plutôt que RC.

vous pouvez faire l'itération de cette façon pour obtenir le BufferedImage, et après que vous avez cela, le reste est facile (vous pouvez utiliser n'importe quel paquet de conversion d'image existant pour le sauver dans un autre format):

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
répondu Eyal 2017-06-06 11:27:04

Ma solution est basée sur une précédente réponse. J'ai utilisé "USWebCoatedSWOP".cpi":

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

en d'autres termes:

  1. ouvrir l'image comme BufferedImage.
  2. va chercher son grille-pain.
  3. inversez le canal noir dans cette grille.
  4. convertir en RVB
3
répondu user1411778 2012-08-03 13:03:12

CMJN vers / de RGB est difficile - vous convertissez entre la couleur additive et la couleur soustractive. Si vous voulez une correspondance exacte, vous devez regarder dans les profils d'espace couleur par appareil. Ce qui semble correct dans un espace de couleur ne l'est généralement pas lorsqu'il est physiquement converti à un autre (c. - à-d. une sortie CMJN correcte-pas un aperçu naïf sur un moniteur).

d'après mon expérience, la conversion de RVB en CMJN tend naïvement à donner une image trop sombre. Étant donné que vous rapportez le contraire dans la direction opposée, il y a probablement une courbe de réglage de luminosité approximative à trouver qui fera un bon travail (mais attention aux non-linéarités étranges dans l'espace de couleur). Si vous avez accès à Photoshop je comprends qu'il a une sorte D'option de prévisualisation CMYK qui pourrait accélérer le processus de comprendre une telle approximation.

1
répondu crazyscot 2010-06-26 16:07:38
    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();
    }
}
}
0
répondu Rohit 2016-07-08 14:13:21